是否有任何自动化方法来实现构造函数后和析构函数前的虚拟方法调用?

发布于 2024-07-29 07:40:13 字数 504 浏览 11 评论 0原文

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

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++ 中是否有任何方法可以自动调用这些方法,以便调用者不必记住调用它们? (我猜没有,但我想我还是会问,以防万一有一些聪明的方法可以做到这一点)

Due to the well-known issues with calling virtual methods from inside constructors and destructors, I commonly end up with classes that need a final-setup method to be called just after their constructor, and a pre-teardown method to be called just before their destructor, like this:

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;

This works, but it carries with it the risk that the caller will forget to call either or both of those methods at the appropriate times.

So the question is: Is there any way in C++ to get those methods to be called automatically, so the caller doesn't have to remember to do call them? (I'm guessing there isn't, but I thought I'd ask anyway just in case there is some clever way to do it)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

请恋爱 2024-08-05 07:40:13

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

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 私有成员。

While there is no automated way, you could force the users hand by denying users access to the destructor on that type and declaring a special delete method. In this method you could do the virtual calls you'd like. Creation can take a similar approach which a static factory method.

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

Now it is not possible to call "delete obj;" unless the call site has access to MyObject private members.

等风也等你 2024-08-05 07:40:13

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

The best I can think of is for you to implement your own smart pointer with a static Create method that news up an instance and calls Initialize, and in its destructor calls AboutToDelete and then delete.

无法言说的痛 2024-08-05 07:40:13

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

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

I used a very carefully designed Create() factory method (static member of each class) to call a constructor and initializer pair in the same order as C# initializes types. It returned a shared_ptr to an instance of the type, guaranteeing a heap allocation. It proved reliable and consistent over time.

The trick: I generated my C++ class declarations from XML...

堇色安年 2024-08-05 07:40:13

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

Except for JavedPar's idea for the pre-destruction method, there is no pre-made solution to easily do two-phase construction/destruction in C++. The most obvious way to do this is to follow the Most Common Answer To Problems In C++: "Add another layer of indirection."
You can wrap objects of this class hierarchy within another object. That object's constructors/destructor could then call these methods. Look into Couplien's letter-envelop idiom, for example, or use the smart pointer approach already suggested.

南城追梦 2024-08-05 07:40:13

http://www.research.att.com/~bs/wrapper.pdf< /a> 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);
}

http://www.research.att.com/~bs/wrapper.pdf This paper from Stroustrup will solve your problem.

I tested this under VS 2008 and on UBUNTU against g++ compiler. It worked fine.

#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);
}
楠木可依 2024-08-05 07:40:13

您可以在类中使用静态函数模板。 与私人医生/医生。
在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();
}

You can use static function template in the class. With private ctor/dtor.
Run on vs2015 community

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();
}
心碎的声音 2024-08-05 07:40:13

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

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

引用它们(合理使用),您有多种选择:

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

详情请参阅他的书。

I was stuck with the same problem, and after a bit of research, I believe there is not any standard solution.

The suggestions that I liked most are the ones provided in the Aleksandrescu et al. book "C++ coding standards" in the item 49.

Quoting them (fair use), you have several options:

  1. Just document it that you need a second method, as you did.
  2. Have another internal state (a boolean) that flags if post-construction has taken place
  3. Use virtual class semantics, in the sense that the constructor of the most-derived class decides which base class to use
  4. Use a factory function.

See his book for details.

婴鹅 2024-08-05 07:40:13

在 C++ 中添加后构造函数的主要问题是,还没有人确定如何处理后后构造函数、后后构造函数等。

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

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

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()

The main problem with adding post-constructors to C++ is that nobody has yet established how to deal with post-post-constructors, post-post-post-constructors, etc.

The underlying theory is that objects have invariants. This invariant is established by the constructor. Once it has been established, methods of that class can be called. With the introduction of designs that would require post-constructors, you are introducing situations in which class invariants do not become established once the constructor has run. Therefore, it would be equally unsafe to allow calls to virtual functions from post-constructors, and you immediately lose the one apparent benefit they seemed to have.

As your example shows (probably without you realizing), they're not needed:

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;

Let's show why these methods are not needed. These two calls can invoke virtual functions from MyObject or one of its bases. However, MyObject::MyObject() can safely call those functions too. There is nothing that happens after MyObject::MyObject() returns which would make obj->Initialize() safe. So either obj->Initialize() is wrong or its call can be moved to MyObject::MyObject(). The same logic applies in reverse to obj->AboutToDelete(). The most derived destructor will run first and it can still call all virtual functions, including AboutToDelete().

治碍 2024-08-05 07:40:13

我在施工时遇到了同样的问题。 这是我使用 C++14 的解决方案。

这个想法是在与最终对象的声明相同(或非常接近)的范围内声明类 Call 的实例,让析构函数调用创建后脚本。

# include <iostream>
# include <cassert>
# include <memory>
# include <typeinfo>

class A;

// This non-template class stores an access to the instance
// on which a procedure must be called after construction
// The functions are defined after A in order to avoid a loop
class Call
{
    protected:
        A* a;
    public:
                       Call();
        virtual        ~Call();
        virtual void   set(A& a_) = 0;
};

// In this class, the Source must be the final type created
template <typename Source>
class Call_ : public Call
{
    static_assert(std::is_final<Source>::value, "");
    public:
        Call_() : Call() {}
        virtual ~Call_() { assert(typeid(*this->a) == typeid(Source)); }
        virtual void set(A& a_) { this->a = &a_; }
};

class A
{
    protected:
        A(Call& call) { std::cout << "Build A" << std::endl; call.set(*this); } // <----
    public:
        A(A const&) { std::cout << "Copy A" << std::endl; }
        virtual ~A() { std::cout << "Delete A" << std::endl; }
        virtual void actions_after_construction() = 0; // post-creation procedure
};

Call::Call() : a(nullptr)
{}

Call::~Call()
{
    assert(this->a);
    this->a->actions_after_construction();
}

class B : public A
{
    protected:
        B(Call& call) : A(call) { std::cout << "Build B" << std::endl; }
    public:
        B(B const& b) : A(b) { std::cout << "Copy B" << std::endl; }
        virtual ~B() { std::cout << "Delete B" << std::endl; }
        virtual void actions_after_construction() { std::cout << "actions by B" << std::endl; }
};

class C final : public B
{
    private:
        C(Call& call) : B(call) { std::cout << "Build C" << std::endl; }
    public:
        C(std::shared_ptr<Call> p_call = std::shared_ptr<Call>(new Call_<C>)) : C(*p_call) {}
        C(C const& c) : B(c) { std::cout << "Copy C" << std::endl; }
        virtual ~C() { std::cout << "Delete C" << std::endl; }
        virtual void actions_after_construction() { std::cout << "actions by C" << std::endl; }
};

class D final : public B
{
    private:
        D(Call& call) : B(call) { std::cout << "Build D" << std::endl; }
    public:
        D(std::shared_ptr<Call> p_call = std::shared_ptr<Call>(new Call_<D>)) : D(*p_call) {}
        D(D const& d) : B(d) { std::cout << "Copy D" << std::endl; }
        virtual ~D() { std::cout << "Delete D" << std::endl; }
        virtual void actions_after_construction() { std::cout << "actions by D" << std::endl; }
};

int main()
{
    { C c; }
    { D d; }
    return 0;
}

I had the same problem for construction. This is my solution using C++14.

The idea is to declare an instance of the class Call in the same (or quite close) scope than the declaration of the final object, letting the destructor call the post-creation script.

# include <iostream>
# include <cassert>
# include <memory>
# include <typeinfo>

class A;

// This non-template class stores an access to the instance
// on which a procedure must be called after construction
// The functions are defined after A in order to avoid a loop
class Call
{
    protected:
        A* a;
    public:
                       Call();
        virtual        ~Call();
        virtual void   set(A& a_) = 0;
};

// In this class, the Source must be the final type created
template <typename Source>
class Call_ : public Call
{
    static_assert(std::is_final<Source>::value, "");
    public:
        Call_() : Call() {}
        virtual ~Call_() { assert(typeid(*this->a) == typeid(Source)); }
        virtual void set(A& a_) { this->a = &a_; }
};

class A
{
    protected:
        A(Call& call) { std::cout << "Build A" << std::endl; call.set(*this); } // <----
    public:
        A(A const&) { std::cout << "Copy A" << std::endl; }
        virtual ~A() { std::cout << "Delete A" << std::endl; }
        virtual void actions_after_construction() = 0; // post-creation procedure
};

Call::Call() : a(nullptr)
{}

Call::~Call()
{
    assert(this->a);
    this->a->actions_after_construction();
}

class B : public A
{
    protected:
        B(Call& call) : A(call) { std::cout << "Build B" << std::endl; }
    public:
        B(B const& b) : A(b) { std::cout << "Copy B" << std::endl; }
        virtual ~B() { std::cout << "Delete B" << std::endl; }
        virtual void actions_after_construction() { std::cout << "actions by B" << std::endl; }
};

class C final : public B
{
    private:
        C(Call& call) : B(call) { std::cout << "Build C" << std::endl; }
    public:
        C(std::shared_ptr<Call> p_call = std::shared_ptr<Call>(new Call_<C>)) : C(*p_call) {}
        C(C const& c) : B(c) { std::cout << "Copy C" << std::endl; }
        virtual ~C() { std::cout << "Delete C" << std::endl; }
        virtual void actions_after_construction() { std::cout << "actions by C" << std::endl; }
};

class D final : public B
{
    private:
        D(Call& call) : B(call) { std::cout << "Build D" << std::endl; }
    public:
        D(std::shared_ptr<Call> p_call = std::shared_ptr<Call>(new Call_<D>)) : D(*p_call) {}
        D(D const& d) : B(d) { std::cout << "Copy D" << std::endl; }
        virtual ~D() { std::cout << "Delete D" << std::endl; }
        virtual void actions_after_construction() { std::cout << "actions by D" << std::endl; }
};

int main()
{
    { C c; }
    { D d; }
    return 0;
}
一杆小烟枪 2024-08-05 07:40:13

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

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

Haven't seen the answer yet, but base classes are only one way to add code in a class hierarchy. You can also create classes designed to be added to the other side of the hierarchy:

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();
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文