- C++11中引入了
override
关键字,现在当你在派生类中重写基类函数时应添加这一关键字。熟悉 Java 或 C# 的同学应该对此不陌生。本节核心内容就是这句话,接下来关于成员函数性质的讨论可能有些偏题,不过笔者认为其中的信息是非常有价值的。 - 函数重载(overload)和函数重写(override),二者名称相似但完全无关,这里不再赘述。
- 以下函数均不会重写,然而也不会有任何报错(甚至连 warning 都没有)。这些都是我们可能无意识会犯的错误。
class Base
{
public:
virtual void mf1() const { cout << "base class mf1" << '\n'; }
virtual void mf2(int x) { cout << "base class mf2" << '\n'; }
virtual void mf3() & { cout << "base class mf3" << '\n'; }
void mf4() { cout << "base class mf4" << '\n'; }
};
class Derived: public Base
{
public:
virtual void mf1() { cout << "derived class mf1" << '\n'; }
virtual void mf2(float x) { cout << "derived class mf2" << '\n'; }
virtual void mf3() && { cout << "derived class mf3" << '\n'; }
void mf4() { cout << "derived class mf4" << '\n'; }
};
- 添加
override
关键字会引起编译错误进行提示:
override
关键字仅在特定上下文中生效,因此如果有旧代码声明了如下函数,迁移到C++11不会引起报错和歧义:
class Warning { // potential legacy class from C++98
public:
...
void override(); // legal in both C++98 and C++11
// (with the same meaning)
};
- 函数可以被重写的条件:
- 基类函数被声明为
virtual
(Java中函数默认如此)。 - 基类函数和派生类函数名称相同(析构函数除外)。
- 基类函数和派生类函数参数类型相同。
- 基类函数和派生类函数
const
性质相同(注:即mf1
后面的const
,代表函数内不能对数据成员进行修改或调用非const函数)。 - 基类函数和派生类函数的返回值类型和异常类型必须是适配的。重点:返回值类型不一定完全相同(identical),只要是满足基类函数出现的地方派生类函数都能替代即可(如基类指针与派生类指针),这种关系称为 covariance,原文中作者用了意思相近的 compatible 一词。当返回值不适配时VS报错如下,其中“协变”应该就是covariance一词的(晦涩的)翻译。
不相同但适配:
- C++11添加规则:函数的引用标识符必须相同。 引用标识符的作用是声明成员函数是被作为左值还是右值的主体(
*this
)被调用时才有效,如下例:
- 基类函数被声明为
- 当成员函数希望对于主体是左值还是右值的调用进行不同处理时(笔者理解:一般是对右值情况可以进行优化),可以使用引用标识符。例如,上例的
data()
函数要返回类中存储的一个成员values
,类型是std::vector<double>
,数组内的数据量可能很大。假设调用语句为auto v = w.data();
,没有引用标识符时,无论调用者w
是左值还是右值,v
都会被复制构造。然而我们能发现,当w
是右值时(例如来自一个工厂函数),该复制纯粹是浪费时间,直接返回value
的右值版本即可,使v
由vector
的移动构造函数初始化。代码如下:
class Widget {
public:
using DataType = std::vector<double>;
DataType data() & {
return values;
}
DataType data() && {
return std::move(values) ;
}
private:
DataType values;
};
-
对以上推论的有效性进行实验:
-
values
初始化为10000个元素,循环执行一百万次,使用C++11chrono
库计时,VS配置设为Release,仅执行不调试,结果如下(执行三次,计毫秒):- 不添加右值引用版函数:5758ms, 5990ms, 5686ms
- 添加右值引用版函数:2209ms, 2107ms, 2025ms
-
性能提升达到一倍以上,可见如此优化的必要性。
总结
- 使用 override 关键字声明重写函数。
- 引用标识符允许区分成员函数在左值还是右值对象上被调用。