复制大对象,c ++

时间:2012-09-17 15:25:39

标签: c++ assignment-operator copying

我确信这已经得到了解答,但我不是程序员,无法找到/理解正确的答案。 假设我有一个庞大的类big,想要重载一个二元运算符,比如operator +

  1. 是否有一种理智的方式让big X=Y+Z直接在X中构建总和,而不是 创建临时对象,将其复制到X然后销毁临时对象?
  2. 我唯一想到的就是将big包装到另一个类small中,该类将包含指向big的指针并添加int use;个引用数大,所以big对象在use==0时被销毁。并添加另一个赋值运算符,比如说<=进行实际复制。我试图实现它(下面)。它似乎有效,但是 我没有经验,我很难预见,会出现什么问题。 不应该有更简单的解决方案吗?
  3. 代码:

    #include <iostream>
    
    // print and execute cmd
    #define Do(cmd) cout << "\n\n\t"<< ++line << ".\t" << #cmd << ";\n" << endl; cmd;
    
    // print small object: name(small.id[big.id,u=big.use,x=big.x])
    #define Show(avar) cout << #avar << "(" << (avar).id << "[" << ((avar).data==NULL?0:(avar).data->id) << ",u=" << ((avar).data==NULL?0:(avar).data->use) << ",x=" << ((avar).data==NULL?0:(avar).data->x) << "])" 
    
    using namespace std;
    
    class big{
    public:
      static int N;   // biggest id in use
      int id;         // unique id for each object
      int use;        // nuber of references to this object
      int x;          // data
      big() __attribute__((noinline))
      {
        id=++N;
        use=1;
        x=0;
        cout << "big.constructor.def: [" << id << ",u=" << use << ",x="<<x<<"]" << endl;
      }
      big(const int& y) __attribute__((noinline))
      {
        id=++N;
        x=y;
        use=1;
        cout << "big.constructor.int: [" << id << ",u=" << use << ",x="<<x<<"]" << endl;
      }
      big(const big& b) __attribute__((noinline))
      {
        id=++N;
        use=1;
        x=b.x;
        cout << "big.constructor.copy: [" << id << ",u=" << use << ",x="<<x<<"]" << endl;
      }
      ~big() __attribute__((noinline))
      {
        if(use>0) throw 99; // destroing referenced data!
        cout << "big.destructor: [" << id << ",u=" << use << ",x="<<x<<"]" << endl;
      }
      friend class small;
    };
    
    class small{
    public:
      static int N;      // biggest id in use
      int id;            // unique id
      big * data;        // reference to the actual data
      small() __attribute__((noinline))
      {
        id=++N;        
        data=NULL;       // contains no data
        cout << "small.constructor.def: ";
        Show(*this)<< endl;
      }
      small(const int& y) __attribute__((noinline))
      {
        id=++N;
        data=new big (y);  // relies on the big constructor
        cout << "small.constructor.int: ";
        Show(*this)<<endl;
      }
      small(const small& y) __attribute__((noinline))
      {
        id=++N;
        data=y.data;      // new object refers to the same data!!
        if(data!=NULL) 
          ++(data->use);  // new reference added;
        cout << "small.constructor.copy: "; 
        Show(y) << "-->";
        Show(*this) << endl;
      }
      ~small(){
        cout << "small.destructor: ";
        Show(*this)<< endl;
        if(data!=NULL){       // is there data?
          --(data->use);      // one reference is destroyed
          if(data->use == 0)  // no references left, kill the data
        delete data;
        }
      }
      const small& operator= (const small& b) __attribute__((noinline))
      {
        cout << "equal: ";
        Show(*this) << " = ";
        Show(b)<<endl;
        if(data != NULL){     // is there data in the target?
          --(data->use);      // one reference is destroyed
          if(data->use == 0)  // no references left, 
        delete data;      // kill the data
        }
        data=b.data;          // target referenses the same data as the source!
        if(data!=NULL) 
          ++(data->use);      // new references added
        cout << "Done equal: "<<endl;    
        return *this;
      }
      // <= will be used for actual copying the data
      const small& operator<= (const small& b) __attribute__((noinline))
      {
        cout << "Copy: ";
        Show(*this) << " <= ";
        Show(b)<<endl;
        if(data != NULL){     // is there data in the target?
          --(data->use);      // one reference is destroyed
          if(data->use == 0)  // no references left, 
        delete data;      // kill the data
        }
        if(b.data==NULL)     // source has no data
          data=NULL;
        else
          data = new big(*(b.data)); // make new copy of the data 
                                     // via big's copy constructor
        cout << "Done copy: "<<endl;    
        return *this;
      }
      small operator+ (const small& b) __attribute__((noinline))
      {
        cout << "Plus: "; 
        Show(*this) << " + ";
        Show(b)<< endl;
        if(this->data == NULL | b.data == NULL) throw 99; // missing data for +
        small ret(data->x);
        ret.data->x += b.data->x;
        cout << "Return: "; Show(ret)<<endl;
        return ret;
      }
    };
    
    
    int big::N=0;
    int small::N=0;
    
    main(){
      int line=0;
    
      Do(small X(5); small Y(6); small Z(7); small W(X));
      Show(X) << endl;
      Show(Y) << endl;
      Show(Z) << endl;
      Show(W) << endl;
    
      Do(X=Y; Z<=Y);
      Show(X)<<endl;  
      Show(Y)<<endl;  // X and Y refer to the same data
      Show(Z)<<endl;  // Z has a copy of data in Y
    
      Do(X=Z; Y=Z);
      Show(X)<<endl;
      Show(Y)<<endl;
      Show(Z)<<endl;  // data previosly in X,Y destroyed
    
      Do(small* U=new small (17); small* T=new small (*U));
      Show(*U) << endl;
      Show(*T) << endl; // U and T refer to the same big
    
      Do(delete U);
      Show(*T) << endl; // big stays since there is another reference to it
    
      Do(delete T);     // big destroyed
    
      Do(X=(Y+Z)+W);
      Show(X)<<endl;
      Show(Y)<<endl;
      Show(Z)<<endl;  // no extra copying of data occures
    
      cout << "\n\tEND\n" << endl;
    }
    

    输出:

    1.  small X(5); small Y(6); small Z(7); small W(X);
    
    big.constructor.int: [1,u=1,x=5]
    small.constructor.int: *this(1[1,u=1,x=5])
    big.constructor.int: [2,u=1,x=6]
    small.constructor.int: *this(2[2,u=1,x=6])
    big.constructor.int: [3,u=1,x=7]
    small.constructor.int: *this(3[3,u=1,x=7])
    small.constructor.copy: y(1[1,u=2,x=5])-->*this(4[1,u=2,x=5])
    X(1[1,u=2,x=5])
    Y(2[2,u=1,x=6])
    Z(3[3,u=1,x=7])
    W(4[1,u=2,x=5])
    
    
        2.  X=Y; Z<=Y;
    
    equal: *this(1[1,u=2,x=5]) = b(2[2,u=1,x=6])
    Done equal: 
    Copy: *this(3[3,u=1,x=7]) <= b(2[2,u=2,x=6])
    big.destructor: [3,u=0,x=7]
    big.constructor.copy: [4,u=1,x=6]
    Done copy: 
    X(1[2,u=2,x=6])
    Y(2[2,u=2,x=6])
    Z(3[4,u=1,x=6])
    
    
        3.  X=Z; Y=Z;
    
    equal: *this(1[2,u=2,x=6]) = b(3[4,u=1,x=6])
    Done equal: 
    equal: *this(2[2,u=1,x=6]) = b(3[4,u=2,x=6])
    big.destructor: [2,u=0,x=6]
    Done equal: 
    X(1[4,u=3,x=6])
    Y(2[4,u=3,x=6])
    Z(3[4,u=3,x=6])
    
    
        4.  small* U=new small (17); small* T=new small (*U);
    
    big.constructor.int: [5,u=1,x=17]
    small.constructor.int: *this(5[5,u=1,x=17])
    small.constructor.copy: y(5[5,u=2,x=17])-->*this(6[5,u=2,x=17])
    *U(5[5,u=2,x=17])
    *T(6[5,u=2,x=17])
    
    
        5.  delete U;
    
    small.destructor: *this(5[5,u=2,x=17])
    *T(6[5,u=1,x=17])
    
    
        6.  delete T;
    
    small.destructor: *this(6[5,u=1,x=17])
    big.destructor: [5,u=0,x=17]
    
    
        7.  X=(Y+Z)+W;
    
    Plus: *this(2[4,u=3,x=6]) + b(3[4,u=3,x=6])
    big.constructor.int: [6,u=1,x=6]
    small.constructor.int: *this(7[6,u=1,x=6])
    Return: ret(7[6,u=1,x=12])
    Plus: *this(7[6,u=1,x=12]) + b(4[1,u=1,x=5])
    big.constructor.int: [7,u=1,x=12]
    small.constructor.int: *this(8[7,u=1,x=12])
    Return: ret(8[7,u=1,x=17])
    equal: *this(1[4,u=3,x=6]) = b(8[7,u=1,x=17])
    Done equal: 
    small.destructor: *this(8[7,u=2,x=17])
    small.destructor: *this(7[6,u=1,x=12])
    big.destructor: [6,u=0,x=12]
    X(1[7,u=1,x=17])
    Y(2[4,u=2,x=6])
    Z(3[4,u=2,x=6])
    
        END
    
    small.destructor: *this(4[1,u=1,x=5])
    big.destructor: [1,u=0,x=5]
    small.destructor: *this(3[4,u=2,x=6])
    small.destructor: *this(2[4,u=1,x=6])
    big.destructor: [4,u=0,x=6]
    small.destructor: *this(1[7,u=1,x=17])
    big.destructor: [7,u=0,x=17]
    

4 个答案:

答案 0 :(得分:3)

有,它被称为copy elision。与此案例特别相关的是return value optimization (RVO) named and return value optimization (NRVO)。这意味着在某些情况下返回值时,允许编译器忽略副本。实施一个天真的加法运算符可能会导致RVO。

请注意,这是编译器允许执行的优化,但不保证会发生。但是C ++具有移动语义,它提供了一种形式的方法,通过这种方式,一个(通常是临时的)对象的底层数据可以“移动”到另一个对象,而不会产生不必要的副本。有一篇关于移动语义的文章here

答案 1 :(得分:1)

如果和是一个复合值,另一种方法是让big::operator+返回一个sumOfBig类的实例,它保留指向Y和Z的指针或引用。

sumOfBig可能包含在需要时动态计算总和成分的成员函数。

答案 2 :(得分:1)

考虑在这种情况下定义和使用+=

Big a, b, c;

而不是:

a = b + c;

执行:

a=b;
a+=c;

+=的示例定义:

Big& Big::operator += (const Big& other)
{
   this->a += other.a;
   // ...
   return *this;
}

您可以根据operator +制作operator +=,使其在逻辑上相同。

答案 3 :(得分:0)

首先,启用优化并查看您的编译器实际的作用:根据juanchopanza的回答,您可以免费获得(N)RVO。

如果您的编译器无法做到这一点,根据Piotr的回答显式删除中间副本可能会改善问题。

如果你真的需要推迟对任意复杂算术表达式的评估,你需要表达式模板(正如Nicola和我在评论中提到的):如果你有可变的源对象,你可能需要表达式模板写入时复制。这些都不是微不足道的。如果你真的需要它们,并且真的找不到已经完成你所需要的库...那么,一旦你研究了它们并开始了,我会留意你的表达模板实现的问题。 / p>