оператор присваивания — копирование больших объектов, переполнение стека

Я уверен, что на этот вопрос ответили, но я не программист и не смог найти / понять подходящий ответ.
Предположим, у меня есть массивный класс big для которого хотел бы перегрузить бинарный оператор, скажем operator +,

  1. Есть ли вменяемый способ сделать big X=Y+Z вложить сумму непосредственно в X, вместо
    создание временного объекта, копирование его в X а потом уничтожить временный?
  2. Единственное, что приходит мне в голову — это завернуть 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]

2

Решение

Есть, это называется копия elision. Особое отношение к этому делу имеют оптимизация именованных значений (RVO) и оптимизация возвращаемых значений (NRVO). Это означает, что компилятору разрешено исключать копии при возврате значений в определенных ситуациях. Реализация наивного сложного оператора может привести к RVO.

Обратите внимание, что это оптимизация, что компилятор позволил делать, но это не гарантировано. Но C ++ имеет семантику перемещения, которая обеспечивает формальные средства, с помощью которых базовые данные одного (обычно временного) объекта могут быть «перемещены» к другому объекту без ненужных копий. Там есть статья о семантике перемещения Вот.

3

Другие решения

Если сумма является составным значением, альтернативный подход будет иметь big::operator+ вернуть экземпляр sumOfBig класс, который хранит указатели или ссылки на Y и Z.

sumOfBig может содержать функции-члены, которые вычисляют составляющие суммы на лету, когда это необходимо.

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 += заставить их делать то же самое логически.

1

Сначала включите оптимизацию и посмотрите какой у вас компилятор на самом деле делает: вы можете получить (N) RVO бесплатно в соответствии с ответом juanchopanza.

Если ваш компилятор не может этого сделать, явное удаление промежуточной копии в соответствии с ответом Петра может улучшить ситуацию.

Если вам действительно нужно отложить оценку произвольно сложных арифметических выражений, вам нужны шаблоны выражений (как мы с Николой упоминали в комментариях): если у вас изменяемые исходные объекты, вам могут понадобиться шаблоны выражений а также копирование при записи. Ни один из них не тривиален. Если вы действительно нуждаетесь в них и действительно не можете найти библиотеку, которая уже делает то, что вам нужно … что ж, я поищу вопросы о реализации вашего шаблона выражения, как только вы их исследуете и начинаете.

0
По вопросам рекламы [email protected]