首页 > 解决方案 > 通过基指针 C++ 设计模式删除派生类

问题描述

现在我不确定我是否只是在这里使用了糟糕的设计模式,所以很高兴接受一些输入,但这就是我想要做的。

我有很多按层次结构排列的类,但是基类由于它们的使用方式,不能有虚拟析构函数。(如果他们能做到这一点会容易得多!)从本质上讲,它们只是数据的聚合。我遇到的问题是,我需要在这些派生类上创建不同的类型和调用方法,还要(之后)调用基类的方法。您可以通过在每个 switch 案例中调用基类方法来解决这个问题,但这看起来很难看,所以我正在寻找一种在 switch 之外调用它们的方法。我在这里遇到的问题是我有指向派生对象的基指针并且不知道它实际上是哪个派生对象,并且由于基没有虚拟析构函数,如果通过基指针删除它会发生内存泄漏。

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。因此,虽然我很欣赏人们说没有虚拟析构函数是一个奇怪的要求,但这是我必须使用的。(哦,在没有虚函数的情况下继承是一个完全合法的用例。

标签: c++smart-pointers

解决方案


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

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

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

推荐阅读