从派生类中删除虚函数

Posted

技术标签:

【中文标题】从派生类中删除虚函数【英文标题】:Delete virtual function from a derived class 【发布时间】:2014-08-27 21:25:55 【问题描述】:

我有一个虚拟基类函数,它永远不应该在特定的派生类中使用。有没有办法“删除”它?我当然可以给它一个空定义,但我宁愿尝试使用它会引发编译时错误。 C++11 delete 说明符似乎是我想要的,但是

class B

    virtual void f();
;

class D : public B

    virtual void f() = delete; //Error
;

不会编译;至少,gcc 明确不会让我删除具有未删除基本版本的函数。是否有其他方法可以获得相同的功能?

【问题讨论】:

这怎么可能?你想D *d = new D(); static_cast<B *>(d)->f() 出现编译器错误吗? @BryanChen 如果它被转换为指向基类的指针,那么它应该像指向基类的指针一样——在这种情况下,D::f 仍然会被调用,所以是的。 那么在这种情况下你会期待什么行为? @Theolodis 更改了我对 Bryan Chen 问题的回答,暂时忘记了派生类函数在将指针转换为基类后仍会被调用。 我认为您误解了delete 关键字的使用。 delete 关键字不是为了从子类中删除方法,这会破坏继承规则,它应该用于禁用特定调用(例如复制调用或从特定类型的参数调用)。 【参考方案1】:

该标准不允许您删除派生类中基类的任何成员,这是有充分理由的:这样做会破坏继承,特别是“is-a”关系。

由于相关原因,它不允许派生类定义在基类中删除的函数: 钩子不再是基类合约的一部分,因此它阻止您依赖不再存在的先前保证。

如果你想变得棘手,你可以强制出错,但它必须是链接时间而不是编译时间: 声明成员函数,但永远不要定义它(虽然这不能 100% 保证适用于虚函数)。 最好还查看 GCC deprecated 属性以获取早期警告__attribute__ ((deprecated))。 详情及类似的MS魔法:C++ mark as deprecated

【讨论】:

好的 +1 链接时间错误建议。我不关心具体使用delete,我只想要功能。 实际上,即使从未调用过f,这样做也会产生“未定义的vtable”错误,如here所示。 @MattPhillips:如果某些虚拟方法的实现不存在,一些编译器/链接器不会抱怨。有时取决于编译器选项。尽管如此,最好的选择始终是弃用。【参考方案2】:

标准不允许这样做,但是您可以使用以下两种解决方法之一来获得类似的行为。

第一种是使用using将方法的可见性更改为私有,从而防止其他人使用它。该解决方案的问题在于,在超类的指针上调用该方法不会导致编译错误。

class B

public:
    virtual void f();
;

class D : public B

private:
    using B::f;
;

到目前为止,我发现在调用Ds 方法时出现编译时错误的最佳解决方案是使用带有继承自false_type 的通用结构的static_assert。只要没有人调用该方法,该结构就会保持未定义,static_assert 就不会失败。

如果方法被调用,但是结构体被定义并且它的值为假,所以static_assert失败。

如果方法没有被调用,但是你尝试在超类的指针上调用它,那么Ds 方法没有被定义并且你得到一个undefined reference 编译错误。

template <typename T>
struct fail : std::false_type 

;

class B

public:
    virtual void f() 
    
    
;

class D : public B

public:
    template<typename T = bool>
    void
    f()
    
        static_assert (fail<T>::value, "Do not use!");
    
;

另一种解决方法是在使用该方法时引发异常,但这只会在运行时引发。

【讨论】:

很好,但B* d = new D(); d-&gt;f(); 将毫无错误地运行(并且B::f 将被调用),但这是我希望发生错误的那种情况。 @MattPhillips 正如您在您的 cmets 中所说的那样,无论如何都会出现这种行为,还是我错了? 不,我永远不想调用基类版本。在您回答之后,我对 Bryan Chen 的回答已更新,在这种情况下我希望出现编译器错误。 我测试了您的最后一个解决方案here,不幸的是,无论是否调用f,它似乎都会导致错误。 @Zingam 如果你输入static_assert(false); 它永远不会编译。模板结构只有在使用方法时才会被初始化。【参考方案3】:

您可以做的只是在派生实现中抛出异常。例如,Java Collections 框架做得非常过分:当对不可变的集合执行更新操作时,相应的方法会简单地抛出一个UnsupportedOperationException。你可以在 C++ 中做同样的事情。

当然,这只会在运行时显示对函数的恶意使用;不是在编译时。但是,使用虚方法,由于多态性,您无论如何都无法在编译时捕获此类错误。例如:

B* b = new D();
b.f();

在这里,您将D 存储在B* 变量中。因此,即使有办法告诉编译器不允许在 D 上调用 f,编译器也无法在此处报告此错误,因为它只能看到 B

【讨论】:

【参考方案4】:

“我有一个虚拟基类函数,它永远不应该在特定的派生类中使用。”

在某些方面这是矛盾的。虚函数的全部意义在于为基类提供的契约提供不同的实现。你试图做的是打破合同。 C++ 语言旨在防止您这样做。这就是为什么当你实例化一个对象时它强制你实现纯虚函数。这就是为什么它不允许您删除部分合同

正在发生的事情是一件好事。它可能会阻止您实施不适当的设计选择。

但是:

有时,有一个什么都不做的空白实现是合适的:

void MyClass::my_virtual_function()

    // nothing here

或者返回“失败”状态的空白实现:

bool MyClass::my_virtual_function()

    return false;

这完全取决于您要做什么。如果你能提供更多关于你想要达到的目标的信息,也许有人可以为你指明正确的方向。

编辑

如果您考虑一下,为了避免调用特定派生类型的函数,调用者需要知道它正在调用什么类型。调用基类引用/指针的全部意义在于您不知道哪个派生类型将接收调用。

【讨论】:

"调用基类的全部意义......" 这适用于一般情况,但并非总是如此;如果它总是正确的,c++ 语言将不包含static_castdynamic_cast。有时派生类标识对调用代码很重要,而这就是其中之一。 直接处理派生类有时很重要。这确实是 dynamic_cast 的用途。但是你不再调用基类引用/指针,所以我的陈述并不真正适用。但我希望它能回答你的问题,为什么它不允许。【参考方案5】:

我有一个虚拟基类函数,不应该在特定派生类中使用

C++11 提供了一个关键字final,它可以防止虚函数被覆盖。

看:http://en.cppreference.com/w/cpp/language/final。

class B

  virtual void f() final;
;

class D : public B

  // virtual void f();  // a compile-time error
  // void f() override; // a compile-time error
  void f(); // non-virtual function, it's ok
;

【讨论】:

这只是确保它没有被覆盖,但函数仍然被继承

以上是关于从派生类中删除虚函数的主要内容,如果未能解决你的问题,请参考以下文章

派生类中的虚函数

虚函数******

强制 Eclipse 为派生类中纯虚函数的实现提供存根

在派生类中使用虚函数

虚函数

派生类中的方法调用算作虚函数调用吗?