【问题标题】:Delete Derived Class Through Base Pointer C++ Design Pattern通过基指针 C++ 设计模式删除派生类
【发布时间】:2019-08-09 12:38:05
【问题描述】:

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

我有很多按层次结构排列的类,但是基类由于它们的使用方式,不能有虚拟析构函数。 (如果他们能做到这一点会容易得多!)从本质上讲,它们只是数据的聚合。我遇到的问题是,我需要在这些派生类上创建不同的类型和调用方法,还要(之后)调用基类的方法。您可以通过在每个 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。因此,虽然我很欣赏人们说没有虚拟析构函数是一个奇怪的要求,但这是我必须使用的。 (哦,它在没有虚函数的情况下具有继承的完全合法的用例。)

【问题讨论】:

  • “但是基类由于它们的使用方式,不能有虚拟析构函数”——为什么不呢?
  • “基类不能有虚拟析构函数”是一个奇怪的要求......你能至少有一个(纯)虚拟方法,如Destroy 或其他东西,可以清理吗?或者,也许您可​​以重新组织您的课程以遵循零规则?
  • 如果你的类不具有多态性,你就不能拥有多态性。您可以选择运行时多态(virtual 成员)或编译类型多态(模板和访问者模式)。但有些东西必须给予。你需要选择一个。
  • 鉴于您的任意“要求”,即基类不能具有虚拟析构函数,使用 delete“通过基类指针”会给出未定义的行为,除非您的代码专门跟踪每个对象的 ACTUAL 类型,将基指针转换为正确类型的指针,然后将其删除。
  • 因为基类没有虚拟析构函数——那么基类就不是为了派生而设计的。如果该类被设计为派生自,那么析构函数将是虚拟的。

标签: c++ smart-pointers


【解决方案1】:

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

另一种选择是切换到使用std::shared_ptrshared_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;
}

【讨论】:

  • 是的,我知道我可以通过拥有一个虚拟析构函数来获得正确的行为,但是就我所做的而言,不可能有任何虚拟函数。没有VTable,它必须完全是POD。这就是困难所在。
  • 这是我一直在寻找的完美答案——该死的我的 unique_ptr> 使用大脑..
  • @TheWelder 不要自责。 unique_ptr 在 90-95% 的情况下都是正确的工具。
【解决方案2】:

您最好重写代码以避免这些问题。

示例 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 设计

这个想法是有一个并行的类层次结构,这样你的原始类型仍然遵守你要求的约束,但有一个可以使用 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...
}

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-05
    • 2013-03-04
    • 2012-01-07
    • 2020-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-06
    相关资源
    最近更新 更多