我确信这已经得到了解答,但我不是程序员,无法找到/理解正确的答案。
假设我有一个庞大的类big
,想要重载一个二元运算符,比如operator +
。
big X=Y+Z
直接在X
中构建总和,而不是
创建临时对象,将其复制到X
然后销毁临时对象?big
包装到另一个类small
中,该类将包含指向big
的指针并添加int use;
个引用数大,所以big
对象在use==0
时被销毁。并添加另一个赋值运算符,比如说<=
进行实际复制。我试图实现它(下面)。它似乎有效,但是
我没有经验,我很难预见,会出现什么问题。
不应该有更简单的解决方案吗?代码:
#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]
答案 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>