Reputation: 45
I want to replace traditional naked pointer usage in a class inheritance situation.
Example for what I mean:
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class Base
{
public:
void doBaseStuff ()
{
cout << "base stuff\n";
}
};
class Derived:public Base
{
public:
void doDerivedStuff ()
{
cout << "derived stuff\n";
}
};
int main ()
{
// vector to hold pointers
vector < Base * >baseCollection;
// generate pointers
Derived *derivedPtr = new Derived;
Base* basePtr = new Base;
// use derived pointer for something
derivedPtr->doDerivedStuff ();
// fill vector with pointers
baseCollection.push_back (basePtr);
baseCollection.push_back (derivedPtr);
// iterate some 'interface' method
for (auto & element:baseCollection)
{
element->doBaseStuff ();
}
return 0;
}
Is refactoring this as shown below the correct way? I use C++17. std::unique_ptr
instead of std::shared_ptr
won't compile in this case (line: baseCollection.push_back(derivedPtr);
).
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
// sample classes
class Base
{
public:
void doBaseStuff ()
{
cout << "base stuff\n";
}
};
class Derived:public Base
{
public:
void doDerivedStuff ()
{
cout << "derived stuff\n";
}
};
int main ()
{
// vector to hold pointers
vector<shared_ptr<Base>> baseCollection;
// generate pointers
shared_ptr<Base> basePtr = make_shared<Base>();
shared_ptr<Derived> derivedPtr = make_shared<Derived>();
// use derived pointer for something
derivedPtr->doDerivedStuff();
// fill vector with pointers
baseCollection.push_back(basePtr);
baseCollection.push_back(derivedPtr); // implicit cast from shared_ptr<Derived> to shared_ptr<Base> !?
// iterate some 'interface' method
for(auto& element : baseCollection)
{
element->doBaseStuff();
}
return 0;
}
The output is the following in both cases, as expected:
derived stuff
base stuff
base stuff
Upvotes: 1
Views: 332
Reputation: 16825
You can use std::unique_ptr
instead of std::shared_ptr
(it is recommended indeed, because ownership is clearly handled in your application, and not left unspecified to a kind-of garbage-collector), but you have to move them into the vector in order to transfer (unique) ownership.
vector<unique_ptr<Base>> baseCollection;
// generate pointers
unique_ptr<Base> basePtr = make_unique<Base>();
unique_ptr<Derived> derivedPtr = make_unique<Derived>();
// use derived pointer for something
derivedPtr->doDerivedStuff();
// fill vector with pointers
baseCollection.push_back(std::move(basePtr));
baseCollection.push_back(std::move(derivedPtr));
Of course, you must not use your basePtr
and derivedPtr
variables afterwards because they don't own these objects anymore.
An alternative would be to use temporaries:
baseCollection.push_back(make_unique<Base>());
baseCollection.push_back(make_unique<Derived>());
Note that when dealing with unique_ptr
s you can still use the raw pointers (with the .get()
member-function) but the meaning is different: I know an object which is owned by someone else (a unique_ptr
here).
And as stated in the comments, in order to ensure a correct destruction, the destructor of Base
should be virtual ~Base()=default;
because the destructor of baseCollection
does not know that the Base
pointer actually points to a Derived
(probably not the same size, dynamic dispatch is needed). This is not related to unique_ptr
nor shared_ptr
; the problem existed with raw-pointers.
And to go further, since we have polymorphic types we should disable (=delete
) copy/move-constructors/assign in order to prevent slicing, but we are far from the original question.
Upvotes: 5
Reputation: 238331
Is refactoring this as shown below the correct way?
The behaviour of the refactored program is different from the first. The first program leaks memory, while the refactored one does not. That's of course typically a good thing as long as it is intentional.
I use C++17. std::unique_ptr instead of std::shared_ptr won't compile in this case.
That's because you make a copy of the pointer, and unique pointer aren't copyable. You can move the pointer instead. However, unique pointers to base have another problem that shared pointers don't have: If you delete a derived object through a pointer to base, and the destructor of base isn't virtual, then the behaviour of the program will be undefined. This can be easily fixed by making the destructor of the base virtual.
So, as long as you don't copy the pointer, and you make the destructor virtual, there would be no need to pay for the overhead of shared ownership.
Upvotes: 2