std::shared_ptr (Alternative Take: Transition From Raw Pointers)#
This is a task that I gave to students, expanding on std::shared_ptr: refactoring memory management in an existing program by replacing raw pointers with something better.
The (stripped-down) program can be called like so,
Using the test/mock implementation of some interface
$ ./program test
Using the real implementation of some interface (usually using some real hardware on a Linux system, like GPIO or I2C)
$ ./program real
Original Version: Raw Pointers#
A heap allocation of either TestImplemention or
RealImplemention is made, depending on the value of the
commandline parameter, and assigned to an Interface (the abstract
base class of both) pointer my_obj.
Problem: avoiding memory leaks is hard - the allocation must be
freed, so we must take care to delete the object that we
allocated.
#include <string>
#include <iostream>
class Interface
{
public:
virtual ~Interface() = default;
virtual void doit() const = 0;
};
class TestImplemention : public Interface
{
public:
~TestImplemention() override {
std::cout << "TestImplemention::~TestImplemention()" << std::endl;
}
void doit() const override {
std::cout << "TestImplemention::doit()" << std::endl;
}
};
class RealImplemention : public Interface
{
public:
RealImplemention(const std::string& bus, unsigned address) { /*...*/ }
~RealImplemention() override {
std::cout << "RealImplemention::~RealImplemention()" << std::endl;
}
void doit() const override {
std::cout << "RealImplemention::doit()" << std::endl;
}
};
int main(int argc, char** argv)
{
if (argc != 2) {
std::cerr << "nix one parameter" << std::endl;
return 1;
}
Interface* my_obj;
std::string how = argv[1]; // "test" or "real"
if (how == "test")
my_obj = new TestImplemention();
else if (how == "real")
my_obj = new RealImplemention(/*bus*/"/dev/i2c-1", /*address*/0x42);
else
std::cerr << "nix good parameter" << how << std::endl;
my_obj->doit(); // <-- use polymorphic object
delete my_obj; // <-- what if I forget this?
return 0;
}
Replacing Raw With Shared Pointers (Clumsy Version)#
Solution: using shared pointers. I the program below we replaced
all raw pointer types with their correspondingly typed
std::shared_ptr<> template instances. As my_obj goes out of
scope (as a consequence of the return statement), the allocation is
freed automatically.
Problem: much redundant writing
#include <string>
#include <iostream>
#include <memory>
class Interface
{
public:
virtual ~Interface() = default;
virtual void doit() const = 0;
};
class TestImplemention : public Interface
{
public:
~TestImplemention() override {
std::cout << "TestImplemention::~TestImplemention()" << std::endl;
}
void doit() const override {
std::cout << "TestImplemention::doit()" << std::endl;
}
};
class RealImplemention : public Interface
{
public:
RealImplemention(const std::string& bus, unsigned address) { /*...*/ }
~RealImplemention() override {
std::cout << "RealImplemention::~RealImplemention()" << std::endl;
}
void doit() const override {
std::cout << "RealImplemention::doit()" << std::endl;
}
};
int main(int argc, char** argv)
{
if (argc != 2) {
std::cerr << "nix one parameter" << std::endl;
return 1;
}
std::shared_ptr<Interface> my_obj; // <-- ex "Interface*"
std::string how = argv[1]; // "test" or "real"
if (how == "test")
my_obj = std::shared_ptr<TestImplemention>(
new TestImplemention()); // <-- this is clumsy
else if (how == "real")
my_obj = std::shared_ptr<RealImplemention>(
new RealImplemention(
/*bus*/"/dev/i2c-1", /*address*/0x42));// <-- this is clumsy
else
std::cerr << "nix good parameter " << how << std::endl;
my_obj->doit();
/**/ // <-- automatic delete
return 0;
}
Using std::make_shared<>() (Less Clumsy)#
Reducing redundancy by using the std::make_shared<>() template
function.
Note:
newis not called directlyThe constructor is not called directly
The parameters of the
RealImplementionconstructor are forwarded through bystd::make_shared<>()(see here for more)
#include <string>
#include <iostream>
#include <memory>
class Interface
{
public:
virtual ~Interface() = default;
virtual void doit() const = 0;
};
class TestImplemention : public Interface
{
public:
~TestImplemention() override {
std::cout << "TestImplemention::~TestImplemention()" << std::endl;
}
void doit() const override {
std::cout << "TestImplemention::doit()" << std::endl;
}
};
class RealImplemention : public Interface
{
public:
RealImplemention(const std::string& bus, unsigned address) { /*...*/ }
~RealImplemention() override {
std::cout << "RealImplemention::~RealImplemention()" << std::endl;
}
void doit() const override {
std::cout << "RealImplemention::doit()" << std::endl;
}
};
int main(int argc, char** argv)
{
if (argc != 2) {
std::cerr << "nix one parameter" << std::endl;
return 1;
}
std::shared_ptr<Interface> my_obj;
std::string how = argv[1]; // "test" or "real"
if (how == "test")
my_obj = std::make_shared<TestImplemention>(); // <-- less clumsy
else if (how == "real")
my_obj = std::make_shared<RealImplemention>(
/*bus*/"/dev/i2c-1", /*address*/0x42); // <-- less clumsy
else
std::cerr << "nix one parameter " << how << std::endl;
my_obj->doit();
return 0;
}