虚函数的override说明符
派生类经常(但不总是)重写它们继承的虚函数。如果派生类没有重写其基类中的虚函数,那么与任何其他成员一样,派生类继承其基类中定义的版本。
派生类可以在其重写的函数上包含关键字virtual
,但编译器并没有对此进行强制要求。新标准允许派生类明确指出它希望成员函数重写它继承的虚函数。通过在参数列表之后指定override
。如果成员是const
或者引用,则在const
或引用限定符之后指定override
。
Code:
class Bulk_quote : public Quote { // Bulk_quote inherits from Quote
Bulk_quote() = default;
Bulk_quote(const std::string&, double, std::size_t, double);
// overrides the base version of function 'net_price'
double net_price(std::size_t) const override;
private:
std::size_t min_qty = 0; // minimum purchase for the discount to apply
double discount = 0.0; // fractional discount to apply
};
通过将类定义为final来阻止继承
有时我们定义一个我们不希望其他人继承的类。或者我们可以定义一个我们不想考虑它是否适合作为基类的类。根据新标准,我们可以通过在类名之后使用关键字final
来阻止类被用作基类:
Code:
class NoDerived final { /* */ }; // NoDerived can't be a base class
class Base { /* */ };
// Last is final; we cannot inherit from Last
class Last final : Base { /* */ }; // Last can't be a base class
class Bad : NoDerived { /* */ }; // error: NoDerived is final
class Bad2 : Last { /* */ }; // error: Last is final
虚函数的override和final说明符
派生类定义一个与其基类中的虚拟同名但具有不同参数列表的函数是合法的。编译器认为这样的函数独立于基类函数。在这种情况下,派生版本不会重写基类中的版本。在实践中,这样的声明通常是一个错误,因为类作者打算从基类重写虚函数但在指定参数列表时出错。
在新标准下,我们可以在派生类中的虚函数上指定override
。这样做可以让我们的意图变得清晰,并且(更重要的是)让编译器为我们找到这些问题。如果标记为override
的函数没有重写现有的虚函数,编译器将拒绝该程序:
Code:
struct B {
virtual void f1(int) const;
virtual void f2();
void f3();
};
struct D1 : B {
void f1(int) const override; // ok: f1 matches f1 in the base
void f2(int) override; // error: B has no f2(int) function
void f3() override; // error: f3 not virtual
void f4() override; // error: B doesn't have a function named f4
};
在D1
中,对f1
指定override
说明符是合法的。基类和派生类中的f1
都是const
型成员,同时接收一个int
参数,返回类型为void
。D1
中的f1
正确重写了继承自B
的虚函数。
D1
中声明的f2
没有匹配B
中的f2
声明,因为B
中定义的版本没有参数而D1
中定义的版本接收一个int
参数。因为声明不匹配,所以D1
中的f2
没有重写B
中的f2
;它是一个新的函数,这个函数刚好重名。因为我们通过关键字override
说明这是一个重写基类中虚函数的成员函数,但是实际上它并没有重写,所以编译器会产生一个错误。
因为仅有虚函数才能被重写,编译器也将拒绝D1
中的f3
,因为在B
中这个函数不是虚函数,所以不能对其进行重写。同样,f4
也将产生一个错误因为B
中没有名字为f4
的函数。
我们也可以指定一个函数为final
。任何尝试重写声明为final
的函数都将被标记为一个错误:
Code:
struct D2 : B {
// inherits f2() and f3() from B and overrides f1(int)
void f1(int) const final; // subsequent classes can't override f1 (int)
};
struct D3 : D2 {
void f2(); // ok: overrides f2 inherited from the indirect base, B
void f1(int) const; // error: D2 declared f2 as final
};
final
和override
说明符出现在参数列表(包括任何const
或引用限定符)之后和尾随返回(trailing return type
)之后。
删除的拷贝控制和继承
合成的默认构造函数或基类或派生类的任何拷贝控制成员可以被定义为已删除。此外,定义基类的方式可能导致派生类成员被定义为已删除:
- 如果基类中的默认构造函数,拷贝构造函数,拷贝赋值运算符或析构函数被删除或不可访问,则派生类中的相应成员被定义为已删除,因为编译器无法使用用于构造,赋值或销毁对象的基类部分的基类成员。
- 如果基类具有不可访问或删除的析构函数,则派生类中的合成默认构造函数和拷贝构造函数将被定义为已删除,因为无法销毁派生对象的基部分。
- 像往常一样,编译器不会合成已删除的
move
操作。如果我们使用= default
来请求move
操作,那么如果基类中的相应操作被删除或不可访问,它将是派生类中的已删除函数,因为基类部分无法移动。如果基类析构函数被删除或无法访问,也将删除移动构造函数。
Code:
class B {
public:
B();
B(const B&) = delete;
// other members, not including a move constructor
};
class D : public B {
// no constructors
};
D d; // ok: D's synthesized default constructor uses B's default constructor
D d2(d); // error: D's synthesized copy constructor is deleted
D d3(std::move(d)); // error: implicitly uses D's deleted copy constructor
例如,此基类B具有可访问的默认构造函数和显式删除的拷贝构造函数。因为定义了拷贝构造函数,所以编译器不会为B类合成移动构造函数。因此,我们既不能移动也不能复制B类对象。如果从B派生的类想要允许复制或移动其对象,那么派生类必须定义这些构造函数的自己版本。当然,该类必须决定如何复制或移动其基类部分中的成员。实际上,如果基类没有默认,复制或移动构造函数,那么它的派生类通常也不会有。
继承的构造函数
在新标准下,派生类可以重用由其直接基类定义的构造函数。类可以仅初始化其直接基类, 类可以仅从其直接基类继承构造函数。类不能继承默认,拷贝和移动构造函数。如果派生类没有直接定义这些构造函数,则编译器会像往常一样合成它们。
派生类通过使用using声明来继承其基类构造函数。作为示例,我们可以定义Bulk_quote
类并继承Disc_quote
类的构造函数:
Code:
class Bulk_quote : public Disc_quote {
public:
using Disc_quote::Disc_quote; // inherit Disc_quote's constructors
double net_price(std::size_t) const;
};
通常,using
声明仅在当前作用域中使名称可见。当应用于构造函数时,using
声明会导致编译器生成代码。编译器生成与基类中的各个构造函数对应的派生构造函数。也就是说,对于基类中的每个构造函数,编译器在派生类中生成具有相同参数列表的构造函数。
这些由编译器产生的构造函数有如下形式:
derived(parms) : base(args) { }
其中derived
是派生类的名称,base
是基类的名称,parms
是构造函数的参数列表,args
将派生构造函数中的参数传递给基础构造函数。在我们的Bulk_quote
类中,继承的构造函数等价于:
Code:
Bulk_quote(const std::string& book, double price,
std::size_t qty, double disc):
Disc_quote(book, price, qty, disc) { }
如果派生类具有自己的任何数据成员,则默认初始化这些成员。
参考文献
[1] Lippman S B , Josée Lajoie, Moo B E . C++ Primer (5th Edition)[J]. 2013.