Идиоматический способ объявить неизменные классы С++

Итак, у меня есть довольно обширный функциональный код, где основным типом данных являются неизменяемые структуры/классы. То, как я объявляю неизменность, является "практически неизменным", делая переменные-члены и любые методы const.

struct RockSolid {
   const float x;
   const float y;
   float MakeHarderConcrete() const { return x + y; }
}

Это на самом деле способ "мы должны это сделать" на С++? Или есть лучший способ?

Ответ 1

То, как вы предложили, прекрасно, за исключением случаев, когда в коде вам нужно назначить переменные RockSolid, например:

RockSolid a(0,1);
RockSolid b(0,1);
a = b;

Это не сработает, так как оператор копирования будет удален компилятором.

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

class RockSolid {
  private:
    float x;
    float y;

  public:
    RockSolid(float _x, float _y) : x(_x), y(_y) {
    }
    float MakeHarderConcrete() const { return x + y; }
    float getX() const { return x; }
    float getY() const { return y; }
 }

Таким образом, ваши объекты RockSolid являются (псевдо) неизменяемыми, но вы все еще можете выполнять задания.

Ответ 2

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

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

В качестве примера:

struct RockSolidLayers {
  const std::vector<RockSolid> layers;
};

мы можем создать один из них, но если у нас есть функция для его создания:

RockSolidLayers make_layers();

он должен (логически) скопировать его содержимое в возвращаемое значение или использовать синтаксис return {}, чтобы напрямую его построить. Снаружи вам нужно либо сделать:

RockSolidLayers&& layers = make_layers();

или снова (логически) copy-construct. Неспособность переместить-конструкцию будет мешать нескольким простым способам оптимального кода.

Теперь обе эти копии построены, но чем более общий случай выполняется - вы не можете перемещать свои данные из одного именованного объекта в другой, так как С++ не имеет операции "уничтожить и переместить", которая требует переменная вне области действия и использует ее для создания чего-то еще.

И случаи, когда С++ неявно перемещают ваш объект (return local_variable; например) до уничтожения, блокируются вашими членами данных const.

В языке, разработанном вокруг неизменяемых данных, он знал бы, что он может "перемещать" ваши данные, несмотря на его (логическую) неизменность.

Один из способов решить эту проблему - использовать кучу и сохранить ваши данные в std::shared_ptr<const Foo>. Теперь значение const отсутствует в данных элемента, а скорее в переменной. Вы также можете открывать только factory функции для каждого из ваших типов, которые возвращают выше shared_ptr<const Foo>, блокируя другую конструкцию.

Такие объекты могут быть скомпилированы с Bar сохранением элементов std::shared_ptr<const Foo>.

Функция, возвращающая std::shared_ptr<const X>, может эффективно перемещать данные, а локальная переменная может переместиться в другую функцию после того, как вы закончите с ней, не имея возможности связываться с "реальными" данными.

Для связанного с ним метода он является idomatic в менее ограниченном С++, чтобы взять такой shared_ptr<const X> и хранить их в пределах типа оболочки, который делает вид, что они не являются неизменяемыми. Когда вы выполняете mutating operaiton, shared_ptr<const X> клонируется и модифицируется, а затем сохраняется. Оптимизация "знает", что shared_ptr<const X> "действительно" a shared_ptr<X> (обратите внимание: убедитесь, что функции factory возвращают a shared_ptr<X> приведение к shared_ptr<const X>, или это на самом деле не так), а когда use_count() равен 1 вместо этого отбрасывает const и изменяет его напрямую. Это реализация метода, известного как "копирование при записи".