Плохо ли использование нового места размещения в качестве оператора присваивания копии?

Иногда я хочу создавать классы/структуры с const элементами. Я понимаю, что это плохая идея по нескольким причинам, но ради аргумента давайте представим, что единственная причина в том, что это делает правильно сформированное operator = хлопотным, если не сказать больше. Тем не менее, я придумал довольно простой обходной путь, как показано в этой структуре:

struct S {

  const int i;

  S(int i) : i(i) {}

  S(const S& other) : i(other.i) {}

  S& operator =(const S& other) {
    new (this) S(other);
    return *this;
  }

};

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

S& operator =(const S& other) {
  const_cast<int&>(i) = other.i;
  return *this;
}

Итак, краткое изложение вопроса таково: есть ли какая-либо основная причина, по которой размещение-новое не должно использоваться для реализации присваивания копирования, чтобы иметь ту же семантику, что и конструкция копирования?


person Anonymous    schedule 12.07.2019    source источник
comment
Интересно, почему вы хотите иметь класс с const участниками и operator=. Это пахнет проблемой дизайна для меня.   -  person Thomas    schedule 12.07.2019
comment
Как я уже сказал, игнорируйте другие проблемы с этим шаблоном. Это гипотетически; На самом деле я не буду его использовать, независимо от ответов на вопрос.   -  person Anonymous    schedule 12.07.2019
comment
См. это и это   -  person NathanOliver    schedule 12.07.2019
comment
Я предлагаю следующее: isocpp.org/wiki/faq/assignment-operators   -  person SergeyA    schedule 12.07.2019
comment
Это не течет? Когда происходит delete, если вы звоните new по заданию?   -  person Martin Véronneau    schedule 12.07.2019
comment
@MartinVéronneau Placement new не выделяет никакой памяти, он просто использует память, которая уже есть.   -  person Anonymous    schedule 12.07.2019
comment
Ох, ладно! Хе. Я думаю, мне нужно изучить это! Нет ничего лучше, чем почувствовать себя старожилом, чем напоминание о том, что вы немного отошли от новых возможностей того самого языка, который используете каждый день.   -  person Martin Véronneau    schedule 12.07.2019
comment
Конструкторы создают новые объекты. Операторы присваивания изменяют существующие объекты. Рано или поздно оператор присваивания, использующий конструктор для создания нового объекта на месте, вас укусит.   -  person Pete Becker    schedule 12.07.2019


Ответы (2)


есть ли какая-то действительно веская причина, почему этого не следует делать?

  1. Вы должны быть абсолютно уверены, что каждый производный класс определяет свой собственный оператор присваивания, даже если он тривиален. Потому что неявно определенный оператор копирования-присваивания производного класса все испортит. Он вызовет S::operator=, который заново создаст объект неправильного типа на его месте.

  2. Такой оператор присваивания "разрушить и построить" не может повторно использоваться ни одним производным классом. Таким образом, вы не только заставляете производные классы предоставлять явный оператор копирования, но и заставляете их придерживаться той же идиомы уничтожения и создания в их операторе присваивания.

  3. Вы должны быть абсолютно уверены, что никакой другой поток не обращается к объекту, пока он уничтожается и создается таким оператором присваивания.

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

  5. С точки зрения производительности он практически не имеет преимуществ перед стандартной идиомой копирования и подкачки. Итак, какой смысл в том, чтобы пройти через всю боль, упомянутую выше?

person Igor G    schedule 12.07.2019

Я не считаю, что новое размещение является проблемой здесь, но const_cast вызывает неопределенное поведение:

C++ 10.1.7.1-4 За исключением того, что любой член класса, объявленный изменяемым (10.1.1), может быть изменен, любая попытка изменить константный объект во время его существования (6.6.3) приводит к неопределенному поведению.

Вам, вероятно, это сойдет с рук, пока компилятор не начнет оптимизировать вещи.

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

person Adam Sosnowski    schedule 12.07.2019
comment
Но вам, вероятно, это сойдет с рук, пока рассматриваемый объект имеет тривиальный деструктор. Только не с этим членом const. - person Nicol Bolas; 12.07.2019
comment
Стоит добавить, что если у вас есть нетривиальный деструктор, вам нужно явно вызвать деструктор: this->~S(); перед использованием размещения new: new (this) S(...); - person alter igel; 12.07.2019