Reputation: 1062
Now I'm not sure if I'm just using a bad design pattern here, so am happy to accept some input on that, but here is what I am trying to do.
I've got quite a few classes that are arranged in a hierarchical structure, but the base classes because of how they are used, cannot have virtual destructors. (It would have been so much easier if they could have!) Essentially they're just aggregates of data. The problem I have is, I need to create varying types and call methods on these derived classes, but also (afterwards) call methods of the base class. You could solve this by calling the base class methods in every switch case, but that seems ugly, so I was looking at a way of calling them outside of the switch. The problem I have here is that I have base pointer pointing to a derived object and no-knowledge of which derived object it actually is and because the base has no virtual destructor, it'll memory leak if deleted through the base pointer. So this is what I came up with:
class Base
{
public:
Base() { std::cout << "Base constructor called" << std::endl; }
~Base() { std::cout << "Base destructor called" << std::endl; }
void SetTimer(void) {}
void SetStatus(void) {}
void SetLogger(void) {}
protected:
uint32_t data1, data_2;
uint32_t data3;
};
class Derived1 : public Base
{
public:
Derived1() { std::cout << "Derived1 constructor called" << std::endl; }
~Derived1() { std::cout << "Derived1 destructor called" << std::endl; }
void Special1(void) { data1 = data2 = 0; }
};
class Derived2 : public Base
{
public:
~Derived2() { std::cout << "Derived2 destructor called" << std::endl; }
void Special2(void) { data3 = 0; }
};
class Derived3 : public Base
{
public:
~Derived3() { std::cout << "Derived3 destructor called" << std::endl; }
void Special3(void) { data1 = data2 = data3 = 0; }
};
//template<typename T>
//struct deleter
//{
// void operator()(T* p) const { delete p; }
//};
int main(int argc, char** argv)
{
int cl = 1;
{
auto deleter = [](auto* t) { delete static_cast<decltype(t)>(t); };
// std::unique_ptr<Base, deleter<Base*>> d1;
std::unique_ptr<Base, decltype(deleter)> d1(new Base(), deleter);
switch (cl)
{
case 1:
{
d1 = std::unique_ptr<Derived1, decltype(deleter)>(new Derived1(), deleter);
// d1 = std::unique_ptr<Derived1, decltype(deleter<Derived1*>)>(new Derived1(), deleter<Derived1*>());
static_cast<Derived1*>(d1.get())->Special1();
break;
}
case 2:
{
d1 = std::unique_ptr<Derived2, decltype(deleter)>(new Derived2(), deleter);
static_cast<Derived2*>(d1.get())->Special2();
break;
}
case 3:
{
d1 = std::unique_ptr<Derived3, decltype(deleter)>(new Derived3(), deleter);
static_cast<Derived3*>(d1.get())->Special3();
break;
}
}
d1.get()->SetLogger();
d1.get()->SetStatus();
d1.get()->SetTimer();
}
return 0;
}
So in each case statement, I have to create a new object type, but need something that I can use later to call base methods on. As you can see in the commented out code, I did think about using a functor to get around the unique_ptr<> declaration, but couldn't think of a good way to make it work.
My biggest issue is with this line:
std::unique_ptr<Base, decltype(deleter)> d1(new Base(), deleter);
It just doesn't seem like its nice to be creating a base class only because I need to supply a deleter at declaration time. As you can see, I thought about the functor approach, but that didn't seem to help as the templated functor would have a different declaration than the std::unique_ptr.
Now I don't know if it's a code issue here or whether I've just chosen a bad design pattern to do this kind of thing or whether the code I've got commented out could be made to work. Anyway, any help would be appreciated.
EDIT EDIT
Thanks for the help, but there must be NO virtual methods or virtual destructors in the class hierarchy because the created object must be completely standard layout compliant. So std::is_standard_layout<>::value should return true. So whilst I appreciate people saying that having no virtual destructors is a strange requirement, it's what I've got to work with. (Oh and it IS a perfectly legitimate use-case to have inheritance without having virtual functions.)
Upvotes: 0
Views: 1012
Reputation: 2623
It would be preferable for you to rewrite your code in a way to avoid those problems.
Example 1: Use a fonction to share common code
void PostProcess(Base &base)
{
base.SetLogger();
base.SetStatus();
base.SetTimer();
}
int main(int argc, char** argv)
{
int cl = 1;
{
switch (cl)
{
case 1:
{
// Example with an object on the stack
Derived1 d1;
d1.Special1();
PostProcess(d1);
break;
}
case 2:
{
// Example using make_unique
auto d2 = std::make_unique<Derived2>();
d2->Special2();
PostProcess(*d2);
break;
}
case 3:
{
// Example similar to your syntax
std::unique_ptr<Derived3> d3(new Derived3());
d3->Special3();
PostProcess(*d3);
break;
}
}
}
return 0;
}
Example2: Use templates to share code
The main idea is to abstract difference so that common code could be used to handle all derived types
// Fix different naming (if you cannot modify existing classes)
void Special(Derived1 &d1) { d1.Special1(); }
void Special(Derived2 &d2) { d2.Special2(); }
void Special(Derived3 &d3) { d3.Special3(); }
// Use a template function to re-use code
template <class T> void Process()
{
auto d = std::make_unique<T>();
Special(*d);
d->SetLogger();
d->SetStatus();
d->SetTimer();
}
At that point, you can modify your switch case like that:
case 1:
Process<Derived1>();
break;
// Similar code for other cases…
Example 3: Create proxy classes to have a OO design
The idea is to have a parallel class hierarchy so that your original types still obey to your required constraint but have a proxy that can use a v-table
class BaseProxy {
public:
virtual ~BaseProxy() { }
virtual void Special() = 0;
};
// Example where you can take a reference to an external object
// that is expected to live longer than the proxy...
class Derived1Proxy : public BaseProxy
{
public:
Derived1Proxy(Derived1 &d1_) : d1(d1_) { }
void Special() override { d1.Special1(); }
private:
Derived1 &d1;
};
// Example where your proxy contain the Derived object
class Derived2Proxy : public BaseProxy
{
public:
Derived2Proxy() { }
void Special() override { d2.Special2(); }
private:
Derived2 d2;
};
// Example where you want to ensure that the derived object live as long as required
class Derived3Proxy : public BaseProxy
{
public:
Derived3Proxy(std::shared_ptr<Derived3> p3_) : p3(p3_) { }
void Special() override { d3->Special3(); }
private:
std::shared_ptr<Derived3> p3;
};
std::unique_ptr<BaseProxy> GetProxy(int c1)
{
case 1:
return std::make_unique<Derived1Proxy>(derived1Object);
// other case...
}
You could adapt that code depending of your need.
Upvotes: 1
Reputation: 180500
You have two ways you can accomplish this. If you want to use a unique_ptr
you need to make a functor that takes an enumeration of what type was made, and then it goes through a switch to call the correct version of delete. This is not the best solution as it requires the enum and the functor be updated every time a new type is added to the hierarchy. The reason you need to do this is because the deleter is part of the unique_ptr
's type.
Another option is to switch to using a std::shared_ptr
. shared_ptr
's deleter is not part of the type, but instead part of the internals. This means that when you create a shared_ptr
to a derived class and store it in a shared_ptr
to the base class, it remembers that it is actually pointing to a derived class. Using that would make you main look like
int main(int argc, char** argv)
{
int cl = 1;
{
std::shared_ptr<Base> d1;
switch (cl)
{
case 1:
{
d1 = std::make_shared<Derived1>();
static_cast<Derived1*>(d1.get())->Special1();
break;
}
case 2:
{
d1 = std::make_shared<Derived2>();
static_cast<Derived2*>(d1.get())->Special2();
break;
}
case 3:
{
d1 = std::make_shared<Derived3>();
static_cast<Derived3*>(d1.get())->Special3();
break;
}
}
d1.get()->SetLogger();
d1.get()->SetStatus();
d1.get()->SetTimer();
}
return 0;
}
Upvotes: 2