17

由于从构造函数和析构函数内部调用虚方法的众所周知的问题,我通常最终得到的类需要在其构造函数之后调用最终设置方法,并在其之前调用预拆卸方法析构函数,像这样:

MyObject * obj = new MyObject;
obj->Initialize();   // virtual method call, required after ctor for (obj) to run properly
[...]
obj->AboutToDelete();  // virtual method call, required before dtor for (obj) to clean up properly
delete obj;

这是可行的,但它带来了调用者会忘记在适当的时间调用其中一个或两个方法的风险。

所以问题是:在 C++ 中有没有办法让这些方法被自动调用,所以调用者不必记住调用它们?(我猜没有,但我想我还是会问一下,以防万一有一些聪明的方法可以做到)

4

9 回答 9

11

虽然没有自动化的方法,但您可以通过拒绝用户访问该类型的析构函数并声明特殊的删除方法来强制用户手动操作。在这种方法中,您可以进行您想要的虚拟呼叫。创建可以采用与静态工厂方法类似的方法。

class MyObject {
  ...
public:
  static MyObject* Create() { 
    MyObject* pObject = new MyObject();
    pObject->Initialize();
    return pObject;
  }
  Delete() {
    this->AboutToDelete();
    delete this;
  }
private:
  MyObject() { ... }
  virtual ~MyObject() { ... }
};

现在不可能调用“delete obj;” 除非调用站点有权访问 MyObject 私有成员。

于 2009-07-20T05:11:45.797 回答
3

我能想到的最好的方法是让您使用静态 Create 方法实现自己的智能指针,该方法更新一个实例并调用 Initialize,并在其析构函数中调用 AboutToDelete 然后删除。

于 2009-07-20T05:10:49.340 回答
2

我使用了一个非常精心设计的Create()工厂方法(每个类的静态成员)来调用构造函数和初始化程序对,其顺序与 C# 初始化类型的顺序相同。它返回一个shared_ptr类型的实例,保证堆分配。随着时间的推移,它被证明是可靠和一致的。

诀窍:我从 XML 生成了我的 C++ 类声明......

于 2009-07-20T05:10:21.387 回答
2

除了 JavedPar 对预销毁方法的想法外,在 C++ 中还没有预制的解决方案可以轻松地进行两阶段构建/销毁。最明显的方法是遵循 C++ 中最常见的问题答案:“添加另一层间接”。您可以将此类层次结构的对象包装在另一个对象中。该对象的构造函数/析构函数然后可以调用这些方法。例如,查看 Couplien 的信封成语,或使用已经建议的智能指针方法。

于 2009-07-20T07:48:41.347 回答
2

http://www.research.att.com/~bs/wrapper.pdf Stroustrup 的这篇论文将解决您的问题。

我在 VS 2008 和 UBUNTU 上针对 g++ 编译器进行了测试。它工作得很好。

#include <iostream>

using namespace std;

template<class T>

class Wrap
{
    typedef int (T::*Method)();
    T* p;
    Method _m;
public:
    Wrap(T*pp, Method m): p(pp), _m(m)  { (p->*_m)(); }
    ~Wrap() { delete p; }
};

class X
{
public:
    typedef int (*Method)();
    virtual int suffix()
    {
        cout << "X::suffix\n";
        return 1;
    }

    virtual void prefix()
    {
        cout << "X::prefix\n"; 
    }

    X() {  cout << "X created\n"; }

    virtual ~X() { prefix(); cout << "X destroyed\n"; }

};

class Y : public X
{
public:
    Y() : X() { cout << "Y created\n"; }
    ~Y() { prefix(); cout << "Y destroyed\n"; }
    void prefix()
    {
        cout << "Y::prefix\n"; 
    }

    int suffix()
    {
        cout << "Y::suffix\n";
        return  1;
    }
};

int main()
{
    Wrap<X> xx(new X, &X::suffix);
    Wrap<X>yy(new Y, &X::suffix);
}
于 2009-07-20T09:35:57.120 回答
1

我遇到了同样的问题,经过一番研究,我相信没有任何标准解决方案。

我最喜欢的建议是 Aleksandrescu 等人提供的建议。书“C++ 编码标准”第 49 条。

引用它们(合理使用),您有几个选择:

  1. 只需记录下您需要第二种方法,就像您所做的那样。
  2. 有另一个内部状态(布尔值)来标记是否进行了后期构建
  3. 使用虚拟类语义,即最派生类的构造函数决定使用哪个基类
  4. 使用工厂函数。

详情见他的书。

于 2012-06-05T08:28:00.427 回答
1

您可以在类中使用静态函数模板。与私人 ctor/dtor。在 vs2015 社区上运行

class A {
    protected:
    A() {}
        virtual ~A() {}
        virtual void onNew() = 0;
        virtual void onDelete() = 0;
    public:

        void destroy() {
            onDelete();
            delete this;
        }

        template <class T> static T* create() {
            static_assert(std::is_base_of<A, T>::value, "T must be a descendant of A");
            T* t = new T();
            t->onNew();
            return t;
        }
   };

class B: public A {
     friend A;

     protected:
          B() {}
          virtual ~B() {}

          virtual void onNew() override {
          }

          virtual void onDelete() override {
          }
};

int main() {
    B* b;
    b = A::create<B>();
    b->destroy();
}
于 2017-01-04T09:54:45.580 回答
0

向 C++ 添加 post-constructors 的主要问题是,还没有人确定如何处理 post-post-constructors、post-post-post-constructors 等。

基本理论是对象具有不变量。这个不变量是由构造函数建立的。一旦建立,就可以调用该类的方法。随着需要后构造函数的设计的引入,您正在引入一旦构造函数运行后类不变量不会建立的情况。因此,允许从后构造函数调用虚函数同样是不安全的,并且您会立即失去它们似乎具有的一个明显好处。

正如您的示例所示(可能您没有意识到),它们不是必需的:

MyObject * obj = new MyObject;
obj->Initialize();   // virtual method call, required after ctor for (obj) to run properly

obj->AboutToDelete();  // virtual method call, required before dtor for (obj) to clean up properly
delete obj;

让我们说明为什么不需要这些方法。这两个调用可以MyObject从其基础或其基础之一调用虚函数。但是,MyObject::MyObject()也可以安全地调用这些函数。MyObject::MyObject()返回后不会发生任何obj->Initialize()安全的事情。所以要么obj->Initialize()是错误的,要么它的调用可以移动到MyObject::MyObject(). 相同的逻辑反过来适用于obj->AboutToDelete()。派生最多的析构函数将首先运行,它仍然可以调用所有虚函数,包括AboutToDelete().

于 2009-07-20T09:51:59.693 回答
-1

还没有看到答案,但基类只是在类层次结构中添加代码的一种方式。您还可以创建旨在添加到层次结构另一侧的类:

template<typename Base> 
class Derived : public Base {
    // You'd need C++0x to solve the forwarding problem correctly.
    Derived() : Base() {
        Initialize();
    }
    template<typename T>
    Derived(T const& t): Base(t) {
        Initialize();
    }
    //etc
private:
    Initialize();
};
于 2009-07-21T07:59:24.977 回答