从标记为final的类的构造函数调用虚函数是不好的做法

时间:2014-06-30 17:06:08

标签: c++ c++11 constructor virtual-functions dynamictype

通常从构造函数中调用虚函数被认为是不好的做法,因为子对象中的被覆盖函数不会被调用,因为尚未构造对象。

但是,请考虑以下类:

class base
{
public:
   base() {}
   ~base() {}

private:
   virtual void startFSM() = 0;
};

class derived final : public base
                    , public fsm_action_interface
{
public:
    derived() : base{}
              , theFSM_{}
    {  startFSM(); }


    /// FSM interface actions

private:
   virtual void startFSM()
   { theFSM_.start(); }

private:
    SomeFSMType theFSM_;
}

在这种情况下,类derived被标记为final,因此不存在其他子对象。因此,虚拟调用将正确解析(到最派生类型)。

它仍被视为不良做法吗?

3 个答案:

答案 0 :(得分:9)

这仍然被认为是不好的做法,因为这种情况几乎总是表明设计不好。你必须对代码中的内容进行评论,以解释为什么在这种情况下有效。

T.C.上面的评论强调了为什么这被认为是不良做法的原因之一。

  

如果在一年之后你决定派生出来,会发生什么   毕竟不应该是最终的?

也就是说,在上面的例子中,模式可以正常工作。这是因为派生类型最多的构造函数是调用虚函数的构造函数。当基类的构造函数调用解析为子类型实现的虚函数时,此问题就会出现。在C ++中,不会调用这样的函数,因为在基类构造期间,此类调用永远不会转到比当前正在执行的构造函数或析构函数更多的派生类。从本质上讲,你最终会得到你没想到的行为。

修改

所有(正确/无错误)C ++实现必须调用当前构造函数中层次结构级别定义的函数版本,而不再进一步。

C++ FAQ Lite在第23.7节中详细介绍了这一点。

Scott Meyers还讨论了Effective C++项目9中构造函数和析构函数调用虚函数的一般问题

答案 1 :(得分:8)

关于

  

通常从构造函数调用虚函数被认为是不好的做法,因为子对象中的被覆盖函数不会被调用,因为尚未构造对象。

事实并非如此。在有能力的C ++程序员中,从构造函数中调用虚函数(纯虚函数除外)通常不被视为不好的做法,因为C ++ 设计来处理它。与Java和C#等语言相比,它可能导致在尚未初始化的派生类子对象上调用方法。

请注意,动态类型的动态调整具有运行时成本。

在一种面向最终效率的语言中,“你不为不使用的东西买单”作为一个主要的指导原则,这意味着它是一个重要且非常有意的特征,而不是一个随意的选择。它仅用于一个目的。即支持这些电话。


关于

  

在这种情况下,派生类被标记为final,因此不存在其他子对象。因此,虚拟调用将正确解析(到最派生类型)。

C ++标准保证在构造 T 的构造时,动态类型为 T

因此,首先解决错误类型没有问题。


关于

  

它仍被视为不良做法吗?

virtual类中声明成员函数final确实是不好的做法,因为这没有意义。“仍然”不是很有意义

抱歉,我没有看到虚拟成员函数是这样继承的。

将成员函数标记为纯虚拟的覆盖或实现的最佳做法是使用关键字 override ,而不是将其标记为virtual

因此:

void startFSM() override
{ theFSM_.start(); }

如果是覆盖/实现,这可以确保编译错误。

答案 2 :(得分:0)

它可以工作,但为什么startFSM()需要virtual?在任何情况下你实际上都不想实际调用除derived::startFSM()之外的任何东西,那么为什么要有任何动态绑定呢?如果你想让它调用与动态绑定方法相同的东西,那么创建另一个名为startFSM_impl()的非虚函数,并让构造函数和startFSM()调用它。

如果可以提供帮助,请务必选择非虚拟虚拟虚拟虚拟虚拟虚拟虚拟虚拟虚拟虚拟虚拟虚拟世