1 删除函数
使用“= delete”将复制构造函数和复制赋值运算符标识为删除函数。
优先使用删除函数,而非private未定义函数。
习惯上,删除函数会被声明为public,而不是private。
这是因为客户代码尝试使用某个成员函数时会先校验可访问性,再校验删除状态。这样一来,当客户代码试图调用某个private删除函数时,编译器只会报错该函数为private,这样得到的错误信息并不完善。
bool isLucky(int number);
bool isLucky(char) = delete;
bool isLucky(bool) = delete;
bool isLucky(double) = delete; // 拒绝float和double
对于模板:
class Widget {
public:
...
template<typename T>
void processPointer(T* ptr)
{ ... }
...
};
template<>
void Widget::processPointer<void>(void*) = delete;
1.2 要点
优先使用删除函数,而非private未定义函数。
任何函数都可以删除,包括非成员函数和模板具现。
2 override
虚函数改写,使得通过基类接口调用派生类函数成为可能。
class Widget {
public:
...
void doWork() &; // 这个版本的doWork仅在*this是左值时调用
void doWork() &&; // 这个版本的doWork仅在*this是右值时调用
};
Widget makeWidget();
Widget w;
w.doWork(); // 以左值调用Widget::doWork()
makeWidget().doWork(); // 以右值调用Widget::doWork()
如果基类中的虚函数带有引用饰词,则派生类要对该函数进行改写版本必须也带有完全相同的引用饰词。
2.2 要点
为意在改写的函数添加override声明
3 noexcept
对不会抛出异常的函数应用noexcept。
3.1 与c++98的throw()对比
int f(int x) throw(); // f不会抛出异常:C++98版本
int f(int x) noexcept; // f不会抛出异常:C++11版本
在C++98异常规格下,调用栈会解开至f的调用方,然后执行一些与本条款无关的动作后,程序终止。
在C++11的异常规格下,运行期行为会稍有不同:程序执行中止前,栈只是可能会开解。(优化器不需要在异常传出函数的前提下,将执行期保持在可开解的状态;也不需要在异常被函数抛出的前提下,保证所有其中的对象以其被构造顺序的逆序完成析构)
3.2 要点
noexcept声明是函数接口的组成部分,这意味着调用方可能会对它有依赖。
相对于不带noexcept声明的函数,带有noexcept声明的函数有更多机会得到优化。
noexcept性质对于移动操作、swap、内存释放函数和析构函数最有价值。
大多数函数都是异常中立的,不具备noexcept性质。
4 constexpr
除了修饰在变量前,表示编译期常量,也可以修饰在函数。
若传给一个constexpr函数的实参值是在编译器已知的,其结果也会在编译期间计算出来,若传入的值有一个或多个是未知的,则结果也会在运行期执行返回。
class Point
{
public:
constexpr Point(double xVal = 0, double yVal = 0) noexcept : x(xVal), y(yVal)
{};
constexpr double xValue() const noexcept { return x; };
constexpr double yValue() const noexcept { return y; };
constexpr void setX(double newX) noexcept { x = newX; }; // c++ 14将void视为字面类型,设置器也可以加上constexpr
constexpr void setY(double newY) noexcept { y = newY; };
private:
double x, y;
}
constexpr
Point midpoint() noexcept
{
return { (p1.xValue() + p2.xValue()) / 2,
(p1.yValue() + p2.yValue()) / 2};
}
constexpr auto mid = midpoint(p1, p2);
constexpr Point p1(9.4, 27.8); // 构造函数声明为constexpr, 入参编译期可知,构造结果也是编译期可知
4.2 要点
constexpr对象都具备const属性,并由编译期已知的值完成初始化。
constexpr函数在调用时若传入的实参值是编译器已知,则会产出编译器结果。
比起非constexpr对象或constexpr函数,constexpr对象或constexpr函数可以用在一个作用域更广的语境中。
5 const
const函数表示这个函数的入参和返回都是常量
const函数的线程安全性需要被保证(否则不同的多个线程在没有同步的情况下读写一块内存,会导致该const函数的返回值被改变(获取多项式的根并不会导致该多项式改变其值)),因此可以使用锁std::mutex或者原子变量std::atomic。
class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const
{
std::lok_guard<std::mutex> g(m); // 添加互斥量
if (!rootsAreValid) { // 若缓存无效则计算/存储roots
...
rootsAreValid = true;
}
return rootVals;
} // 解锁互斥量
private:
mutable std::mutex m;
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
}
添加mutable是因为加锁和解锁都不是const成员函数所为,如果不加mutable,m在roots内会被当作一个const对象处理。
(std::mutex是一个只移型别,只能移动不能复制。将m加入Polynomial之后,Polynomial也成为只移型别)
class Widget
{
public:
...
int magicValue() const
{
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cachedValue = val1 + val2; // 或者调换顺序 cachedValid = true;
cachedValid = true; // 或者调换顺序 cachedValue = val1 + val2;
return cachedValue;
}
}
private:
mutable std::atomic<bool> cacheValid{ false };
mutable std::atomic<int> cachedValue;
}
如果使用std::atomic,那么对于上述代码的两条相互有关联的语句,不能约束他们的线程安全性。
5.2 要点
保证const成员的线程安全性,除非可以确信他们不会用在并发环境中。
对于单个要求同步的变量,使用std::atomic就足够了,相较于互斥量性能更好。
对于两个或更多变量或内存区域需要作为一整个单位进行操作时,就要动用互斥量。