Recently, I have been abstracting and cleaning up a project of mine to get it ready for new features. I came across a design problem when I realized that I wanted to manage pointers to a graphics context and a window as abstract classes. I have searched for answers but haven't found anything satisfying yet. I have a situation where I'm creating a window that extends the window class and the graphics context class like so...
class base_a
{
// base_a virtual methods
};
class base_b
{
// base_b virtual methods
};
class derived : public base_a, public base_b
{
//other methods
};
int main ()
{
derived* d = new derived();
// This is what I want to do
std::shared_ptr<base_a> ptr1 (d);
std::shared_ptr<base_b> ptr2 (d);
}
While this more or less works during the lifetime of the object, it becomes a problem when destructing since depending on the order of destruction you are potentially deleting empty memory. It's useful to have the pointers to both as I need access to the virtual functions and I would like to keep the graphics context and the window decoupled if possible. Is there a way to do this?
Edit: Changed unique_ptr
to shared_ptr
as the former was incorrect
Use a shared_ptr
to manage ownership of the derived
class. Then use std::shared_ptr
's aliasing constructor to share ownership of the derived
instance through a pointer to one of the base classes. Here's what that might look like:
class base_a {
public:
int x = 1;
};
class base_b {
public:
int y = 2;
};
class derived : public base_a, public base_b {
public:
int z = 3;
};
int main() {
auto d_ptr = std::make_shared<derived>();
auto a_ptr = std::shared_ptr<base_a>(d_ptr, static_cast<base_a*>(d_ptr.get()));
auto b_ptr = std::shared_ptr<base_b>(d_ptr, static_cast<base_b*>(d_ptr.get()));
...
This provides safe access to the base classes without risking early or double deletion. Only when the last of all these pointers goes out of scope will the derived
instance (and both of it base class objects) be destroyed.
std::cout << "x : " << d_ptr->x << '\n';
std::cout << "y : " << d_ptr->y << '\n';
std::cout << "z : " << d_ptr->z << '\n';
std::cout << "---\n";
a_ptr->x = 4;
b_ptr->y = 5;
std::cout << "x : " << d_ptr->x << '\n';
std::cout << "y : " << d_ptr->y << '\n';
std::cout << "z : " << d_ptr->z << '\n';
Output:
x : 1
y : 2
z : 3
---
x : 4
y : 5
z : 3
EDIT: while the above holds true for member objects and base class sub-objects, shared_ptr
already defines conversions for base class pointers, as was kindly pointed out by @BenVoigt. This simplifies the code to:
auto d_ptr = std::make_shared<derived>();
auto a_ptr = std::shared_ptr<base_a>(d_ptr);
auto b_ptr = std::shared_ptr<base_b>(d_ptr);