与c ++`override` /`final`说明符相反的是什么?

时间:2016-07-29 10:23:28

标签: c++ c++11 inheritance class-hierarchy modifiers

  • 在c ++ 11中,override specifier可以防止不覆盖预期的虚拟基本功能(因为签名不匹配)。
  • final specifier可以防止无意中覆盖派生类中的函数。

=>是否有一个说明符(类似于firstno_override)可以防止覆盖未知的基本函数?

当虚拟函数被添加到基类中时,我希望得到编译器错误,该基类具有与派生类中现有虚函数相同的签名。

编辑4 :为了使这个问题变得简单并且答案相关,这里又是

原始伪代码

  • abstract class B : Aprivate: virtual void fooHasBeenDone() = 0;
  • class C : B实施private: virtual void fooHasBeenDone() override { react(); }
  • 现在class A获得了新的private: virtual void fooHasBeenDone();
  • 但新A::foo可能与原始B::foo不同。

具体示例

  • abstract class B : Avirtual void showPath() = 0;使用PainterPath
  • class C : B实施virtual void showPath() override { mPath.setVisible(); }
  • 现在class A获得一个新的virtual void showPath();,意思是文件路径
  • 现在,当A调用showPath()时,B显示painterPath而不是某个文件路径。

当然这是错误的,然后我应该将B::showPath()重命名为B::showPainterPath()并实施B::showPath() override。我希望得到编译器的通知。

这是一个编译现实世界的例子

#include <iostream>
#define A_WITH_SHOWPATH

class A
{
#ifdef A_WITH_SHOWPATH
public:
    void setPath(std::string const &filepath) {
        std::cout << "File path set to '" << filepath << "'. Display it:\n";
        showPath();
    }
    // to be called from outside, supposed to display file path
    virtual void showPath() {
        std::cout << "Displaying not implemented.\n";
    }
#else
    // has no showPath() function
#endif  
};

class B : public A
{
public:
    virtual void showPath() = 0; // to be called from outside
};

class C1 : public B {
public:
    virtual void showPath() override {
        std::cout << "C1 showing painter path as graphic\n";
    }
};

class C2 : public B {
public:
    virtual void showPath() override {
        std::cout << "C2 showing painter path as widget\n";
    }
};


int main() {
    B* b1 = new C1();
    B* b2 = new C2();

    std::cout << "Should say 'C1 showing painter path as graphic':\n";
    b1->showPath();
    std::cout << "---------------------------\n";
    std::cout << "Should say 'C2 showing painter path as widget':\n";
    b2->showPath();
    std::cout << "---------------------------\n";

#ifdef A_WITH_SHOWPATH
    std::cout << "Should give compiler warning\n or say \"File path set to 'Test'. Display it:\"\n and \"Displaying not implemented.\",\n but not \"C1 showing painter path as graphic\":\n";
    b1->setPath("Test");
    std::cout << "# Calling setPath(\"Test\") on a B pointer now also displays the\n#  PainterPath, which is not the intended behavior.\n";
    std::cout << "# The setPath() function in B should be marked to never override\n#  any function from the base class.\n";
    std::cout << "---------------------------\n";
#endif
    return 0;
}

运行它并查看文本输出。

供参考,具有特定用例(PainterPath实例)的旧示例:

https://ideone.com/6q0cPD(链接可能已过期)

4 个答案:

答案 0 :(得分:2)

没有。

将虚函数添加到与子类中的虚函数具有相同签名的基类不能打破任何现有功能,除非添加该虚拟函数将基类转换为多态类型。因此,在常态中,它是良性的,并且最纯粹的人会争辩说,添加语言功能以防止这种情况将是毫无意义的。

(当然你可以标记你的新函数Id Name DisplayOrder 100 Home 1 101 Products 2 102 Contact 4 103 Career 3 只是为了检查一个子类函数是不是要破坏它。)

您唯一的选择是使用代码分析工具。

(请注意,VS2012没有实现,甚至声称实现了C ++ 11标准,尽管它确实有一些。)

答案 1 :(得分:2)

firstno_override这样的说明符的设施不存在。可能是因为它可能会造成混乱。但是,通过改变方法可以很容易地实现。

应该使用final说明符在基类中添加任何新方法。这将有助于为任何匹配的签名获取编译器错误。因为,它会使后续的派生类方法签名自动成为同类中的“第一”。之后 final关键字可以删除,因为它仅用于“第一手验证”。

放置&amp;在新添加的基本方法类似于使用debug(final)选项编译二进制文件之后删除g++ -g关键字,这有助于您修复错误。在生产中,删除调试选项以进行优化。

从你的例子:

class A {};  // no method, no worry

class B {
  public: virtual void showPath() = 0;  // ok
};
...

现在我不小心在A中添加了类似的方法,导致错误:

class A {
  public: virtual void showPath() final;  // same signature by chance
  // remove the `final` specifier once the signature is negotiated
}; 
class B {
  public: virtual void showPath() = 0;  // ERROR
};

A::showPath()&amp;之间的签名现有的B::showPath()必须经过谈判和然后通过删除final说明符来继续。

答案 2 :(得分:2)

这个答案是社区维基,因为它结合了所有其他答案。请提供对您有用的具体答案以及此答案。

  1. 不,没有firstno_override等说明符。 (answer)
  2. 您应尽可能频繁地使用override说明符。
    如果可用,Qt的macro Q_DECL_OVERRIDE会扩展为override 如果不可用,至少用注释标记每个覆盖功能。
  3. 如果您这样做,会有编辑器标记警告缺少override
    Clang now has -Winconsistent-missing-override, and newer GCCs have -Wsuggest-override.
    我不知道VS2012标志。随意编辑。
  4. 您可以通过添加基类无法知道的“秘密”来模仿所需的行为。 (answer)
    这在非常具体的用例中很有用,但通常会破坏虚拟的概念(请参阅其他答案的评论)。
  5. 如果您不拥有基类并且存在冲突(例如编译器警告),则需要在所有派生类中重命名虚拟函数。
  6. 如果您拥有基类,则可以暂时final添加到任何新的虚拟功能。 (answer)
    代码编译后没有错误,您知道任何派生类中都不存在该名称和签名的功能,您可以再次删除final
  7. ...我想我会开始将第一个虚拟函数标记为DECL_FIRST。也许在将来会有一种独立于编译器的方法来检查它。

答案 3 :(得分:1)

C ++似乎没有提供开箱即用的手段。但你可以模仿它如下:

template<class Base>
class Derived : public Base
{
private:
    struct DontOverride {};

public:
    // This function will never override a function from Base
    void foo(DontOverride dummy = DontOverride())
    {
    }
};

如果您打算引入 new 虚拟函数,请按以下方式执行:

template<class Base>
class Derived : public Base
{
protected:
    struct NewVirtualFunction {};

public:
    // This function will never override a function from Base
    // but can be overriden by subclasses of Derived
    virtual void foo(NewVirtualFunction dummy = NewVirtualFunction())
    {
    }
};