Размещение нового, чтобы избежать копирования конструктора

У меня есть простой класс, который содержит указатель на один из его собственных членов:

struct X {
int val;
int* pVal;
X(int v) : val(v), pVal(&val) {}
}

X x(1);

У меня есть такой код:

void foo() {
doStuffWith(x);
x = X(2); // completely discard the old value of X, and start again
doStuffWith(x);
}

Я беспокоюсь, что когда х переназначен, x.pVal будет неверно указывать на члена временного X(2) если оптимизация возвращаемого значения не происходит.

Я понимаю, что мог бы написать конструктор копирования, чтобы это исправить. Тем не менее, кажется, что в первую очередь делать копию неэффективно, а не создавать объект в нужном месте в памяти для начала.


Целесообразно ли здесь использовать новый оператор размещения? Или это имеет нежелательные последствия для деструкторов?

void foo() {
doStuffWith(x);
new (&x) X(2); // completely discard the old value of X, and start again
doStuffWith(x);
}

2

Решение

Самый очевидный (и, вероятно, самый эффективный) способ сделать эту работу — предоставить операторы назначения копирования и конструирования копирования, чтобы «делать правильные вещи», что-то в этом общем порядке:

struct X {
int val;
int* pVal;

X(int v) : val(v), pVal(&val) {}
X(X const &other) : val(other.val), pVal(&val) {}

// pVal was already set by ctor, so just ignore it:
X &operator=(X const &other) { val = other.val; return *this; }

// and allow assignment directly from an int:
X &operator=(int n) { val = n; return *this; }
};

Тогда остальную часть кода можно просто скопировать / назначить X объекты без прыжков через обручи, чтобы предотвратить коррупцию.

3

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

нет

Это не разрушит старую ценность x, но вы правы по умолчанию копия задания Оператор не будет делать то, что вы хотите.

1

Для меня это кажется вполне приемлемым решением, если деструктор X будет вызван перед размещением нового. Это синтаксически разрешено, даже деструктор фактически не указан для класса.

struct X {
int val;
int* pVal;
X(int v) : val(v), pVal(&val) {}
};

X x(1);

void foo() {
doStuffWith(x);
x.~X();
new (&x) X(2);
doStuffWith(x);
}

В этой форме это правильный способ повторно использовать память для любого объекта (но только если ctor объекта не может выбросить! В противном случае UB может произойти при закрытии программы, то есть двойном вызове деструктора).

Действительно, равенство указателей, переданных и возвращенных из размещения новой в нем форме без массива, гарантируется стандартом:

18.6.1.3 Формы размещения

void * оператор new (размер std :: size_t, void * ptr) noexcept;

Возвращает: ptr.

Примечания: Преднамеренно не выполняет никаких других действий.

(и результат преобразования в void *, а затем обратно в тот же
тип указателя также гарантированно совпадает с указателем источника)

Однако, чтобы избежать неправильного использования класса, было бы более безопасно либо определить назначение копирования и конструктор копирования, либо объявить этот класс некопируемым (с удаленными).

И только последний (не копируемый) случай может рассматриваться как основание для использования нового размещения.

Несмотря на то, что я далёк от продвижения размещения нового для общего пользования, оно выражает намерение повторно использовать память объекта напрямую и не полагается на какую-либо оптимизацию. Конструктор копирования и назначение копирования, конечно, более безопасны, но не выражают это намерение точно: «копия» на самом деле не нужна, новый объект должен быть создан вместо старого.

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