跳转到内容

更多 C++ 惯用法/复制并交换

来自 Wikibooks,开放世界中的开放书籍

复制并交换

[编辑 | 编辑源代码]

创建重载赋值运算符的异常安全实现。

也称为

[编辑 | 编辑源代码]

创建临时对象并交换

异常安全性是使用异常来指示“异常”情况的可靠 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 引用形式接受参数。

已知用法

[编辑 | 编辑源代码]
[编辑 | 编辑源代码]

参考文献

[编辑 | 编辑源代码]
华夏公益教科书