У меня есть простой класс, который содержит указатель на один из его собственных членов:
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);
}
Самый очевидный (и, вероятно, самый эффективный) способ сделать эту работу — предоставить операторы назначения копирования и конструирования копирования, чтобы «делать правильные вещи», что-то в этом общем порядке:
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
объекты без прыжков через обручи, чтобы предотвратить коррупцию.
Это не разрушит старую ценность x
, но вы правы по умолчанию копия задания Оператор не будет делать то, что вы хотите.
Для меня это кажется вполне приемлемым решением, если деструктор 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 *, а затем обратно в тот же
тип указателя также гарантированно совпадает с указателем источника)
Однако, чтобы избежать неправильного использования класса, было бы более безопасно либо определить назначение копирования и конструктор копирования, либо объявить этот класс некопируемым (с удаленными).
И только последний (не копируемый) случай может рассматриваться как основание для использования нового размещения.
Несмотря на то, что я далёк от продвижения размещения нового для общего пользования, оно выражает намерение повторно использовать память объекта напрямую и не полагается на какую-либо оптимизацию. Конструктор копирования и назначение копирования, конечно, более безопасны, но не выражают это намерение точно: «копия» на самом деле не нужна, новый объект должен быть создан вместо старого.