更多 C++ 惯用法/复制并交换
创建重载赋值运算符的异常安全实现。
创建临时对象并交换
异常安全性是使用异常来指示“异常”情况的可靠 C++ 软件的非常重要的基石。至少有 3 种异常安全性级别:基本、强和无抛出。基本异常安全性应该始终提供,因为它通常很容易实现。保证强异常安全性可能并非在所有情况下都可行。复制并交换惯用法允许以强异常安全性优雅地实现赋值运算符。
创建临时对象并交换惯用法在放弃当前资源之前获取新的资源。为了获取新的资源,它使用 RAII 惯用法。如果成功获取新的资源,它将使用无抛出交换惯用法交换资源。最后,旧资源作为第一步使用 RAII 的副作用而被释放。
class String
{
char * str;
public:
String & operator=(const String & s)
{
String temp(s); // Copy-constructor -- RAII
temp.swap(*this); // Non-throwing swap
return *this;
}// Old resources released when destructor of temp is called.
void swap(String & s) noexcept // Also see the non-throwing swap idiom
{
std::swap(this->str, s.str);
}
};
上述实现也可能存在一些变体。对自赋值的检查并不是严格必要的,但在(很少发生的)自赋值情况下可以提高一些性能。
class String
{
char * str;
public:
String & operator=(const String & s)
{
if (this != &s)
{
String(s).swap(*this); //Copy-constructor and non-throwing swap
}
// Old resources are released with the destruction of the temporary above
return *this;
}
void swap(String & s) noexcept // Also see non-throwing swap idiom
{
std::swap(this->str, s.str);
}
};
复制省略和复制并交换惯用法
严格来说,在赋值运算符中显式创建临时对象是不必要的。赋值运算符的参数(右侧)可以按值传递给函数。参数本身用作临时对象。
String & operator = (String s) // the pass-by-value parameter serves as a temporary
{
s.swap (*this); // Non-throwing swap
return *this;
}// Old resources released when destructor of s is called.
这不仅仅是方便的问题,事实上是一种优化。如果参数 (s) 绑定到一个左值(另一个非 const 对象),在创建参数 (s) 时会自动创建一个对象的副本。但是,当 s 绑定到一个右值(临时对象,字面量)时,复制通常会被省略,这可以节省对复制构造函数和析构函数的调用。在赋值运算符的早期版本中,参数以 const 引用形式接受,当引用绑定到右值时,复制省略不会发生。这会导致创建和销毁一个额外的对象。
在 C++11 中,这样的赋值运算符被称为统一赋值运算符,因为它消除了编写两种不同赋值运算符的需要:复制赋值和移动赋值。只要一个类具有移动构造函数,C++11 编译器就会始终使用它来优化从另一个临时对象(右值)创建副本。复制省略是与非 C++11 编译器中的类似优化,以实现相同的效果。
String createString(); // a function that returns a String object.
String s;
s = createString();
// right hand side is a rvalue. Pass-by-value style assignment operator
// could be more efficient than pass-by-const-reference style assignment
// operator.
并非每个类都从这种赋值运算符中受益。考虑一个 String 赋值运算符,它只在现有内存不足以容纳右侧 String 对象的副本时才释放旧内存并分配新内存。为了实现这种优化,必须编写一个自定义赋值运算符。由于新的 String 副本将使内存分配优化失效,因此此自定义赋值运算符必须避免将参数复制到任何临时 String,特别是需要按 const 引用形式接受参数。