使用下面给出的结构定义...
struct A {
virtual void hello() = 0;
};
方法1:
struct B : public A {
virtual void hello() { ... }
};
方法2:
struct B : public A {
void hello() { ... }
};
这两种覆盖hello函数的方式之间有什么区别吗?
使用下面给出的结构定义...
struct A {
virtual void hello() = 0;
};
方法1:
struct B : public A {
virtual void hello() { ... }
};
方法2:
struct B : public A {
void hello() { ... }
};
这两种覆盖hello函数的方式之间有什么区别吗?
Answers:
他们是完全一样的。它们之间没有什么区别,只是第一种方法需要更多的键入并且可能更清晰。
override
关键字。
函数的“虚拟性”是隐式传播的,但是,如果virtual
未显式使用关键字,则我使用的至少一个编译器将生成警告,因此,如果仅使编译器保持安静状态,则可能要使用它。
从纯粹的风格角度来看,包括virtual
关键字在内显然向用户“宣传”了该功能是虚拟的事实。这对于任何进一步细分B的人都非常重要,而不必检查A的定义。对于较深的类层次结构,这变得尤为重要。
不,不需要virtual
派生类的虚函数重写上的关键字。但是值得一提的是相关的陷阱:无法覆盖虚拟函数。
在未能覆盖,如果你打算在派生类中重写一个虚函数,但做出一个错误的签名,以便它声明了一个新的和不同的虚函数发生。该函数可能是基类函数的重载,或者名称可能有所不同。无论您是否virtual
在派生类函数声明中使用关键字,编译器都将无法告诉您您打算重写基类中的函数。
但是,值得庆幸的是,C ++ 11 显式覆盖语言功能解决了该陷阱,该功能允许源代码明确指定成员函数旨在覆盖基类函数:
struct Base {
virtual void some_func(float);
};
struct Derived : Base {
virtual void some_func(int) override; // ill-formed - doesn't override a base class method
};
编译器将发出编译时错误,并且编程错误将立即显而易见(也许Derived中的函数应该以a float
作为参数)。
请参考WP:C ++ 11。
添加“虚拟”关键字是一种很好的做法,因为它可以提高可读性,但这不是必需的。默认情况下,在基类中声明为virtual且在派生类中具有相同签名的函数被视为“ virtual”。
当您拥有模板并开始将基类作为模板参数时,会有很大的不同:
struct None {};
template<typename... Interfaces>
struct B : public Interfaces
{
void hello() { ... }
};
struct A {
virtual void hello() = 0;
};
template<typename... Interfaces>
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly
{
b.hello(); // indirect, non-virtual call
}
void hello(const A& a)
{
a.hello(); // Indirect virtual call, inlining is impossible in general
}
int main()
{
B<None> b; // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually
B<None>* pb = &b;
B<None>& rb = b;
b.hello(); // direct call
pb->hello(); // pb-relative non-virtual call (1 redirection)
rb->hello(); // non-virtual call (1 redirection unless optimized out)
t_hello(b); // works as expected, one redirection
// hello(b); // compile-time error
B<A> ba; // Ok, vtable generated, sizeof(b) >= sizeof(void*)
B<None>* pba = &ba;
B<None>& rba = ba;
ba.hello(); // still can be a direct call, exact type of ba is deducible
pba->hello(); // pba-relative virtual call (usually 3 redirections)
rba->hello(); // rba-relative virtual call (usually 3 redirections unless optimized out to 2)
//t_hello(b); // compile-time error (unless you add support for const A& in t_hello as well)
hello(ba);
}
它的有趣之处在于,您现在可以在定义类之后定义接口和非接口函数。这对于库之间的交互接口很有用(不要将其作为单个库的标准设计过程来依赖)。您无需花任何钱就可以在所有课程中使用它-您甚至可能typedef
如果您愿意, B。
请注意,如果执行此操作,则可能也希望将复制/移动构造函数声明为模板:允许从不同的接口构造允许您在不同的B<>
类型之间“转换” 。
这是值得怀疑是否应该添加支持const A&
在t_hello()
。进行这种重写的通常原因是从基于继承的专业转向基于模板的专业,主要是出于性能方面的考虑。如果您继续支持旧界面,则几乎无法检测(或阻止)旧用法。
该virtual
关键字应该被添加到一个基类的功能,使他们重写。在您的示例中,struct A
是基类。virtual
在派生类中使用这些功能没有任何意义。但是,如果您希望派生类本身也是基类,并且希望该函数可重写,则必须将其放置在virtual
那里。
struct B : public A {
virtual void hello() { ... }
};
struct C : public B {
void hello() { ... }
};
这里C
继承自B
,所以B
不是基类(它也是派生类),C
而是派生类。继承图如下所示:
A
^
|
B
^
|
C
因此,您应该将virtual
函数的前面放在可能有子对象的潜在基类内部。virtual
让您的孩子超越您的职能。将virtual
函数放在派生类内部没有什么错,但这不是必需的。不过还是建议这样做,因为如果有人想从您的派生类继承,他们将不满意方法重写无法按预期工作。
所以放 virtual
,在涉及继承的所有类中的函数之前都应在函数的前面,除非您确定该类不会有需要重写基类的函数的子代。这是一个好习惯。
我一定会为子类添加Virtual关键字,因为