【问题标题】:C++ : Automatically run function when derived class is constructedC++:构造派生类时自动运行函数
【发布时间】:2016-06-10 01:35:49
【问题描述】:

所以我最近不小心从基类的构造函数中调用了一些虚函数,即Calling virtual functions inside constructors

我意识到我不应该这样做,因为不会调用虚函数的覆盖,但是我怎样才能实现一些类似的功能呢?我的用例是我希望在构造对象时运行特定函数,并且我不希望编写派生类的人不必担心这是在做什么(因为他们当然可以在它们的派生类构造函数)。但是,需要依次调用的函数恰好调用了一个虚函数,我想让派生类能够根据需要重写它。

但是因为调用了一个虚函数,我不能把这个函数放在基类的构造函数中,然后让它自动运行。所以我似乎被困住了。

还有其他方法可以实现我想要的吗?

编辑:我碰巧正在使用 CRTP 从基类访问派生类中的其他方法,我可以在构造函数中使用它而不是虚函数吗?还是存在同样的问题?我想如果被调用的函数是静态的,也许它可以工作?

edit2:也刚刚发现了这个类似的问题:Call virtual method immediately after construction

【问题讨论】:

  • 如果函数调用是构造派生类所必需的,并且是特定于该类的,听起来应该从派生类的构造函数中调用它。从技术上尚不存在的对象调用方法并不是一个好主意。
  • 嗯,我要运行的函数是一种运行时验证例程,以确保他们编写的派生类符合某些要求。它需要访问虚拟功能以检查它们是否正常工作。
  • 在这种情况下,像@Jarod42 这样的工厂方法可能是你最好的选择。

标签: c++ inheritance constructor virtual


【解决方案1】:

如果你看看其他人是如何解决这个问题的,你会发现他们只是将调用初始化函数的责任转移给了客户端。以 MFC 的CWnd 为例:你有构造函数,你有Create,你必须调用一个虚函数才能有一个正确的CWnd 实例化:“这些是我的规则:构造,然后初始化;服从,否则你会惹上麻烦的”

是的,它很容易出错,但它比替代方案更好:“有人建议此规则是一个实现工件。不是这样。事实上,实现从构造函数调用虚函数的不安全规则与从其他函数调用虚函数一样容易得多。但是,这意味着不能编写任何虚函数来依赖基类建立的不变量。那将是一团糟。” - Stroustrup。我认为他的意思是,将虚拟表指针设置为指向派生类的 VT 会更容易,而不是在构造函数调用从基数向下时不断将其更改为当前类的 VT。

我意识到我不应该这样做,因为不会调用虚函数的覆盖,...

假设对虚函数的调用会按照您想要的方式工作,由于不变量,您不应该这样做。

class B // written by you
{
public:
  B() { f(); }
  virtual void f() {}
};

class D : public B // written by client
{
  int* p;
public:
  D() : p( new int ) {}
  void f() override { *p = 10; } // relies on correct initialization of p  
};

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

如果可以通过D 的VT 从B 调用D::f 怎么办?您将使用未初始化的指针,这很可能会导致崩溃。

...但是我怎样才能实现一些类似的功能呢?

如果你愿意打破规则,我想也许可以得到想要的虚表的地址,并从构造函数中调用虚函数。

【讨论】:

    【解决方案2】:

    我希望在构造一个对象时运行一个特定的函数,[...它]反过来又调用了一个虚函数,我想让派生类能够在需要时覆盖它。

    如果你愿意忍受两个限制,这很容易做到:

    1. 整个类层次结构中的构造函数必须是非公共的,因此
    2. 必须使用工厂模板类来构造派生类。

    这里,“特定函数”是Base::check,虚函数是Base::method

    首先,我们建立基类。它只需要满足两个要求:

    1. 它必须是MakeBase,它的检查器类。我假设您希望 Base::check 方法是私有的并且只能由工厂使用。如果它是公开的,你当然不需要MakeBase
    2. 构造函数必须受到保护。

    https://github.com/KubaO/stackoverflown/tree/master/questions/imbue-constructor-35658459

    #include <iostream>
    #include <utility>
    #include <type_traits>
    using namespace std;
    
    class Base {
       friend class MakeBase;
       void check() {
          cout << "check()" << endl;
          method();
       }
    protected:
       Base() { cout << "Base()" << endl; }
    public:
       virtual ~Base() {}
       virtual void method() {}
    };
    

    模板化 CRTP 工厂派生自与 Base 为朋友的基类,因此可以访问私有检查器方法;它还可以访问受保护的构造函数以构造任何派生类。

    class MakeBase {
    protected:
       static void check(Base * b) { b->check(); }
    };
    

    如果您无意中将工厂类用于不是从Base 派生的类,工厂类可能会发出可读的编译时错误消息:

    template <class C> class Make : public C, MakeBase {
    public:
       template <typename... Args> Make(Args&&... args) : C(std::forward<Args>(args)...) {
          static_assert(std::is_base_of<Base, C>::value,
                        "Make requires a class derived from Base");
          check(this);
       }
    };
    

    派生类必须有一个受保护的构造函数:

    class Derived : public Base {
       int a;
    protected:
       Derived(int a) : a(a) { cout << "Derived() " << endl; }
       void method() override { cout << ">" << a << "<" << endl; }
    };
    
    int main()
    {
       Make<Derived> d(3);
    }
    

    输出:

    Base()
    Derived() 
    check()
    >3<
    

    【讨论】:

      【解决方案3】:

      您似乎想要这个,或者需要更多详细信息。

      class B 
      {
          void templateMethod()
          {
              foo();
              bar();
          }
      
          virtual void foo() = 0;
          virtual void bar() = 0;
      };   
      
      class D : public B
      {
      public:
          D()
          {
              templateMethod();           
          }
      
          virtual void foo()
          {
              cout << "D::foo()";
          }
      
          virtual void bar()
          {
              cout << "D::bar()";
          }
      };
      

      【讨论】:

        【解决方案4】:

        没有简单的方法可以做到这一点。一种选择是使用所谓的虚拟构造函数习语,隐藏基类的所有构造函数,而是公开静态“创建” - 这将动态创建一个对象,调用您的虚拟覆盖并返回(智能)指针。

        这很丑陋,更重要的是,它将您限制在动态创建的对象中,这不是最好的事情。

        但是,最好的解决方案是尽可能少地使用 OOP。 C++ 的优势(与流行的看法相反)在于它的非 OOP 特定特征。想想看——标准库中唯一的多态类是流,每个人都讨厌它(因为它们是多态的!)

        【讨论】:

        • 我喜欢流:p。但是嗯,好吧,我同意这个解决方案听起来是个坏主意……不过不知道如何重新考虑这个设计。
        • 我从来不用担心他们的速度。他们很慢?
        • @BenFarmer,令人讨厌。
        • 而根本原因是它们的多态性?
        • @KubaOber,让它变得更加糟糕。比你需要手动调用析构函数......只是不要这样做。
        【解决方案5】:

        如果确实需要,您可以访问工厂。

        你可以这样做:

        template <typename Derived, typename ... Args>
        std::unique_ptr<Derived> Make(Args&&... args)
        {
            auto derived = std::make_unique<Derived>(std::forward<Args>(args));
            derived->init(); // virtual call
            return derived;
        }
        

        【讨论】:

        • 嗯,很有趣,但遗憾的是没有工厂可以合作。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-11-28
        • 2018-07-11
        • 2023-04-07
        • 1970-01-01
        • 2015-08-18
        • 2016-10-06
        • 2023-04-10
        相关资源
        最近更新 更多