通过基指针C++设计模式删除派生类

Delete Derived Class Through Base Pointer C++ Design Pattern

本文关键字:删除 派生 设计模式 C++ 指针      更新时间:2023-10-16

现在我不确定我是否只是在这里使用了糟糕的设计模式,所以我很高兴接受一些意见,但这就是我正在尝试做的事情。

我有相当多的类以分层结构排列,但由于基类的使用方式,它们不能有虚拟析构函数。 (如果他们可以的话,那就容易多了! 从本质上讲,它们只是数据的聚合。 我遇到的问题是,我需要在这些派生类上创建不同的类型和调用方法,以及(之后(调用基类的方法。 您可以通过在每个开关情况下调用基类方法来解决此问题,但这似乎很丑陋,所以我正在寻找一种在开关之外调用它们的方法。 我在这里遇到的问题是,我有指向派生对象的基本指针,并且不知道它实际上是哪个派生对象,并且由于基本没有虚拟析构函数,如果通过基指针删除,它将内存泄漏。 所以这就是我想出的:

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;
}

因此,在每个 case 语句中,我都必须创建一个新的对象类型,但需要一些稍后可以使用的东西来调用基方法。 正如您在注释掉的代码中看到的那样,我确实考虑过使用函子来绕过unique_ptr<>声明,但想不出使其工作的好方法。

我最大的问题是这一行:

std::unique_ptr<Base, decltype(deleter)> d1(new Base(), deleter);

仅仅因为我需要在声明时提供一个删除器而创建一个基类似乎并不好。 如您所见,我考虑了函子方法,但这似乎没有帮助,因为模板化函子的声明与 std::unique_ptr 不同。

现在我不知道这是这里的代码问题,还是我只是选择了一个糟糕的设计模式来做这种事情,或者我注释掉的代码是否可以工作。 无论如何,任何帮助将不胜感激。

编辑编辑

感谢您的帮助,但类层次结构中不得有虚拟方法或虚拟析构函数,因为创建的对象必须完全符合标准布局。 所以std::is_standard_layout<>::value应该返回 true。 因此,虽然我很欣赏人们说没有虚拟析构函数是一个奇怪的要求,但这是我必须使用的。 (哦,这是一个完全合法的用例,可以在没有虚拟功能的情况下继承。

您有两种方法可以实现此目的。 如果要使用unique_ptr则需要创建一个函子,该函子枚举所创建的类型,然后通过开关来调用正确的删除版本。 这不是最好的解决方案,因为它要求每次将新类型添加到层次结构时更新枚举和函子。 您需要这样做的原因是删除器是unique_ptr类型的一部分。

另一种选择是切换到使用std::shared_ptr.shared_ptr的删除器不是类型的一部分,而是内部的一部分。 这意味着,当您创建派生类的shared_ptr并将其存储在基类的shared_ptr中时,它会记住它实际上指向派生类。 使用它会使你看起来像

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;
}

最好是以某种方式重写代码以避免这些问题。

示例 1:使用函数共享通用代码

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;
}

示例 2:使用模板共享代码

主要思想是抽象差异,以便通用代码可用于处理所有派生类型

// 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();
}

此时,您可以像这样修改开关大小写:

case 1:
Process<Derived1>();
break;
// Similar code for other cases…

示例 3:创建代理类以具有 OO 设计

这个想法是有一个平行的类层次结构,以便你的原始类型仍然服从你所需的约束,但有一个可以使用向量表的代理。

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...
}

您可以根据需要调整该代码。