从派生类中删除虚函数
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;
;
到目前为止,我发现在调用D
s 方法时出现编译时错误的最佳解决方案是使用带有继承自false_type
的通用结构的static_assert
。只要没有人调用该方法,该结构就会保持未定义,static_assert
就不会失败。
如果方法被调用,但是结构体被定义并且它的值为假,所以static_assert
失败。
如果方法没有被调用,但是你尝试在超类的指针上调用它,那么D
s 方法没有被定义并且你得到一个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->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_cast
和dynamic_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
;
【讨论】:
这只是确保它没有被覆盖,但函数仍然被继承以上是关于从派生类中删除虚函数的主要内容,如果未能解决你的问题,请参考以下文章