C++ C++&引用;虚拟的;派生类中函数的关键字。有必要吗?

C++ C++&引用;虚拟的;派生类中函数的关键字。有必要吗?,c++,overriding,virtual-functions,C++,Overriding,Virtual Functions,使用下面给出的结构定义 struct A { virtual void hello() = 0; }; 方法1: 方法2: 这两种重写hello函数的方法有什么不同吗?它们完全相同。它们之间没有区别,只是第一种方法需要更多的键入,并且可能更清晰。当您在派生类中编写虚拟或忽略它时,编译器没有区别 但是您需要查看基类才能获得这些信息。因此,我建议在派生类中也添加virtual关键字,如果您想向人类显示此函数是虚拟的。函数的“虚拟性”是隐式传播的,但是如果未显式使用virtual关键字,我使

使用下面给出的结构定义

struct A {
    virtual void hello() = 0;
};
方法1:

方法2:


这两种重写hello函数的方法有什么不同吗?

它们完全相同。它们之间没有区别,只是第一种方法需要更多的键入,并且可能更清晰。

当您在派生类中编写
虚拟
或忽略它时,编译器没有区别


但是您需要查看基类才能获得这些信息。因此,我建议在派生类中也添加
virtual
关键字,如果您想向人类显示此函数是虚拟的。

函数的“虚拟性”是隐式传播的,但是如果未显式使用
virtual
关键字,我使用的至少一个编译器将生成警告,因此,如果只是为了保持编译器的安静,您可能需要使用它


从纯粹的文体角度来看,包括
virtual
关键字,可以清楚地向用户“宣传”功能是虚拟的这一事实。这对于任何进一步细分为B类而不必检查A的定义的人来说都很重要。对于深层类层次结构,这一点尤为重要。

添加“virtual”关键字是一种良好的做法,因为它可以提高可读性,但不是必需的。默认情况下,在基类中声明为virtual且在派生类中具有相同签名的函数被视为“virtual”。

我肯定会为子类包含virtual关键字,因为

  • 一,。可读性
  • 二,。这个子类可以进一步派生,您不希望进一步派生类的构造函数调用这个虚拟函数

派生类中不需要使用
virtual
关键字。下面是支持文档,从C++草稿标准(N33 37)(强调矿山):

10.3虚拟功能

2如果虚拟成员函数
vf
在类
Base
和类
Derived
中声明,直接或间接从
Base
派生,则成员函数
vf
具有相同名称、参数类型列表(8.3.5)、cv限定符和ref限定符(或没有相同的)由于声明了
Base::vf
,那么
Derived::vf
也是虚拟的(无论是否如此声明),并且它覆盖了
Base::vf


否,派生类的虚拟函数重写上的
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
};
编译器将发出编译时错误,编程错误将立即显现出来(可能派生函数中的函数应该使用
浮点
作为参数)


请参阅。

当您有模板并开始将基类作为模板参数时,会有很大的不同:

struct None{};
模板
结构B:公共接口
{
void hello(){…}
};
结构A{
虚空hello()=0;
};
模板
void t_hello(const B&B)//为每一组接口生成不同的代码(基于vtable的智能编译器可能会将其减少到2);t_hello和b.hello()都可以正确内联
{
b、 hello();//间接的非虚拟调用
}
无效你好(const A&A)
{
a、 hello();//间接虚拟调用,通常不可能内联
}
int main()
{
B;//好的,没有生成vtable,空基类优化有效,sizeof(B)==1
B*pb=&B;
B&rb=B;
b、 hello();//直接呼叫
pb->hello();//pb相对非虚拟调用(1重定向)
rb->hello();//非虚拟调用(1个重定向,除非已优化)
t_hello(b);//按预期工作,一个重定向
//hello(b);//编译时错误
B ba;//确定,生成vtable,sizeof(B)>=sizeof(void*)
B*pba=&ba;
B&rba=ba;
ba.hello();//仍然可以是直接调用,ba的确切类型是可以推断的
pba->hello();//pba相对虚拟调用(通常为3个重定向)
rba->hello();//rba相对虚拟调用(通常为3个重定向,除非优化为2个)
//t_hello(b);//编译时错误(除非您在t_hello中也添加了对常量A的支持)
你好(文学士);
}
有趣的是,您现在可以在以后定义类时定义接口函数和非接口函数。这对于库之间的交互接口很有用(不要将其作为单个库的标准设计过程)。在所有类中都允许这样做不需要花费任何费用-如果愿意,您甚至可以
typedef
B

注意,如果您这样做,您可能也希望将复制/移动构造函数声明为模板:允许从不同接口进行构造允许您在不同的
B
类型之间进行“强制转换”

您是否应该在
t\u hello()
中添加对
const A&
的支持是值得怀疑的。这种重写的通常原因是远离基于继承的spe
struct B : public A {
    void hello() { ... }
};
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
};
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);
}
struct B : public A {
    virtual void hello() { ... }
};

struct C : public B {
    void hello() { ... }
};
A
^
|
B
^
|
C