【问题标题】:What is copy elision and how does it optimize the copy-and-swap idiom?什么是复制省略,它如何优化复制和交换习语?
【发布时间】:2011-01-09 18:28:42
【问题描述】:

我正在阅读Copy and Swap

我尝试阅读有关 Copy Elision 的一些链接,但无法正确理解其含义。有人可以解释一下这个优化是什么,尤其是下面的文字是什么意思

这不仅仅是为了方便,实际上是一种优化。如果参数(s)绑定到左值(另一个非常量对象),则在创建参数时自动制作对象的副本。但是,当 s 绑定到右值(临时对象,文字)时,副本通常会被省略,这样可以节省对复制构造函数和析构函数的调用。在参数被接受为 const 引用的赋值运算符的早期版本中,当引用绑定到右值时不会发生复制省略。这会导致创建和销毁一个额外的对象。

【问题讨论】:

标签: c++ optimization copy-and-swap copy-elision


【解决方案1】:

复制构造函数的存在是为了制作副本。理论上当你写这样一行时:

CLASS c(foo());

编译器必须调用复制构造函数将foo() 的返回值复制到c

复制省略是一种跳过调用复制构造函数的技术,以免支付开销。

例如,编译器可以安排foo()将其返回值直接构造成c

这是另一个例子。假设你有一个函数:

void doit(CLASS c);

如果你用实参调用它,编译器必须调用复制构造函数,这样原始参数就不能被修改:

CLASS c1;
doit(c1);

但现在考虑一个不同的例子,假设你这样调用你的函数:

doit(c1 + c1);

operator+ 将不得不创建一个临时对象(一个右值)。在调用doit() 之前,编译器可以传递由operator+ 创建的临时变量并将其传递给doit(),而不是调用复制构造函数。

【讨论】:

    【解决方案2】:

    这是一个例子:

    #include <vector>
    #include <climits>
    
    class BigCounter {
     public:
       BigCounter &operator =(BigCounter b) {
          swap(b);
          return *this;
       }
    
       BigCounter next() const;
    
       void swap(BigCounter &b) {
          vals_.swap(b);
       }
    
     private:
       typedef ::std::vector<unsigned int> valvec_t;
       valvec_t vals_;
    };
    
    BigCounter BigCounter::next() const
    {
       BigCounter newcounter(*this);
       unsigned int carry = 1;
       for (valvec_t::iterator i = newcounter.vals_.begin();
            carry > 0 && i != newcounter.vals_.end();
            ++i)
       {
          if (*i <= (UINT_MAX - carry)) {
             *i += carry;
          } else {
             *i += carry;
             carry = 1;
          }
       }
       if (carry > 0) {
          newcounter.vals_.push_back(carry);
       }
       return newcounter;
    }
    
    void someFunction()
    {
        BigCounter loopcount;
        while (true) {
           loopcount = loopcount.next();
        }
    }
    

    somefunction 中,loopcount = loopcount.next(); 行从复制省略中受益匪浅。如果不允许复制省略,则该行将需要复制构造函数的 3 次调用以及对析构函数的关联调用。在允许复制省略的情况下,它可以减少到 1 次复制构造函数调用,即 BigCount::next() 内部的显式调用,其中声明了 newcounter

    如果 operator = 被这样声明和定义:

    BigCounter &BigCounter::operator =(const BigCounter &b) {
       BigCounter tmp(b);
       swap(tmp);
       return *this;
    }
    

    必须有 2 次复制构造函数调用,即使使用复制省略也是如此。一个构造newcounter,另一个构造tmp。如果没有复制省略,仍然会是 3。这就是为什么声明 operator = 所以它的参数需要调用复制构造可以是对赋值运算符使用“复制和交换”习语时的优化。当复制构造函数被调用来构造一个参数时,它的调用可能会被省略,但如果它被调用来创建一个局部变量,它可能不会。

    【讨论】:

      猜你喜欢
      • 2021-10-29
      相关资源
      最近更新 更多