复制省略和虚拟克隆

时间:2016-07-03 21:54:13

标签: c++ copy-elision

如何避免在以下情况下进行不必要的复制? A类包含指向大对象的基类型指针。

class A{
  BigBaseClass *ptr;
  A(const BigBaseClass& ob);
  ~A(){delete ptr;}
};

有时我需要复制对象 ob 。所以我实现了虚拟克隆:

class BigBaseClass{
   virtual BigBaseClass* clone() {return new BigBaseClass(*this);}
};
class BigDerivedClass : BigBaseClass{
  virtual BigDerivedClass* clone() {return new BigDerivedClass(*this);}
};
A::A(const BigBaseClass& ob):ptr(ob.clone(){}

但有时我会创建临时的BigDerivedClass对象并用它来构造A类:

A a{BigDerivedClass()};

BigDerivedClass f(){
     BigDerivedClass b;
     /*constructing object*/
     return b;
   }
   A a{f()};

此处无需复制创建的对象,然后将其删除。可以直接在堆中创建此对象,并将其地址存储在 a.ptr 中。

但是我似乎不太可能编译器足够聪明,可以在这里实现复制省略(或者是吗?)。那么你会建议避免这种不必要的复制?

2 个答案:

答案 0 :(得分:4)

编译器将通过clone()来构建副本:只有在特定情况下才允许复制省略。在允许编译器进行复制省略的所有情况下,所涉及对象的生命周期完全由编译器控制。这四种情况(详见12.8 [class.copy]第8段):

  1. 按值返回本地名称。
  2. 投掷本地对象。
  3. 复制未绑定到引用的临时对象。
  4. 按价值追赶。
  5. 即使在这些情况下,复制 - 省略也适用的细节有点不重要。无论如何,return new T(*this);并不适合任何这些情况。

    典型的大型对象不会将其数据作为对象的一部分。相反,它们通常包含一些可以移动的数据结构。如果您希望在使用A{f()}时保留简单性而不想复制f()的结果,则可以使用移动构造函数调用virtual函数来传输内容而不是复制它:

    #include <utility>
    
    class BigBaseClass {
    public:
        virtual ~BigBaseClass() {}
        virtual BigBaseClass* clone() const = 0;
        virtual BigBaseClass* transfer() && = 0;
    };
    class A{
        BigBaseClass *ptr;
    public:
        A(BigBaseClass&& obj): ptr(std::move(obj).transfer()) {}
        A(BigBaseClass const& obj): ptr(obj.clone()) {}
        ~A(){delete ptr;}
    };
    class BigDerivedClass
        : public BigBaseClass {
        BigDerivedClass(BigDerivedClass const&); // copy the content
        BigDerivedClass(BigDerivedClass&&);      // transfer the content
        BigDerivedClass* clone() const { return new BigDerivedClass(*this); }
        BigDerivedClass* transfer() && { return new BigDerivedClass(std::move(*this)); }
    };
    
    BigDerivedClass f() {
        return BigDerivedClass();
    }
    
    int main()
    {
        A a{f()};
    }
    

    移动构造是否有助于复制大对象取决于对象在内部的实现方式。如果它们的对象基本上只包含几个指向实际大数据的指针,那么移动构造应该避免任何相关的成本,因为与设置实际数据相比,传输指针可以忽略不计。如果数据实际上保存在对象中,那么转移将无法提供帮助(尽管由于各种原因,这通常是一个坏主意)。

答案 1 :(得分:0)

.outer {
  width: 100%;
  overflow: hidden;
  background: green;
}
.inner {
  width: 10000em; /*super wide*/
  margin-left: -5000em; /*half the width*/
  background: #cbb;
  position: relative;
  left: 50%;
  text-align: center;
}

你可能会认为移动语义是一个好主意,但是在这种情况下并不能真正起作用,因为BigBaseClass是一个基类,将BigDerivedClass移动到BigBaseClass只会移动BigBaseClass部分。但是使用智能指针也是个好主意,除非你确定你的其余代码没有例外。