неизменность — Как создать неизменяемые структуры в C ++?

Исходя из C # фона, я привык к практике создания неизменяемые структуры.
Поэтому, когда я начал программировать на C ++, я попытался сделать то же самое с типами, которые я обычно передавал по значению.

У меня была простая структура, которая представляет пользовательский индекс и просто упаковывает целое число. Потому что в C ++ const Ключевое слово чем-то похоже на readonly в C # казалось разумным реализовать мою структуру следующим образом:

struct MyIndex
{
MyIndex(int value) :
Value(value)
{
}

const int Value;
};

Проблема этого подхода заключается в том, что эта структура ведет себя совершенно иначе, чем неизменная структура в C #. Мало того, что поле Значение MyIndex переменная не может быть изменена, но также любые существующие MyIndex переменная (локальная переменная или переменная поля) даже не может быть заменена другой MyIndex-пример.

Поэтому следующие примеры кода не компилируются:

а) Использование индекса в качестве переменной цикла.

MyIndex index(0);

while(someCondition)
{
DoWork();

index = MyIndex(index.Value + 1); // This does not compile!
}

б) Изменение поля члена типа MyIndex

class MyClass
{
MyIndex Index;

void SetMyIndex(MyIndex index)
{
Index = index; // This does not compile!
}
};

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

ошибка C2582: функция operator = недоступна в MyIndex

Что является причиной этого? Почему нельзя заменить переменные другим экземпляром, несмотря на то, что они не являются const? А что означает ошибка сборки точно? Что это такое operator = функционировать?

18

Решение

Причина этого заключается в том, что в C ++ после создания переменной (локальной или полевой переменной) ее нельзя «заменить» другим экземпляром, можно изменить только ее состояние. Так что в этой строке

index = MyIndex(1);

не новый экземпляр MyIndex созданный (с его конструктором), а существующая переменная индекса должна быть изменено с этими оператор копирования. Пропажа operator = функция является оператором копирования копии MyIndex тип, которого ему не хватает, потому что он не генерируется автоматически если тип имеет поле const.

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

MyIndex& operator=(const MyIndex& other)
{
if(this != &other)
{
Value = other.Value; // Can't do this, because Value is const!
}

return *this;
}

Так что, если мы хотим, чтобы наши структуры были практически неизменяемый, но ведущий себя аналогично неизменяемым структурам в C #, мы должны использовать другой подход, который состоит в том, чтобы сделать каждое поле нашей структуры частный, и пометить каждую функцию нашего класса const,
Реализация MyIndex с таким подходом:

struct MyIndex
{
MyIndex(int value) :
value(value)
{
}

// We have to make a getter to make it accessible.
int Value() const
{
return value;
}

private:
int value;
};

Строго говоря, структура MyIndex не является неизменной, но ее состояние не может быть изменено с помощью чего-либо доступного извне (кроме его оператор автоматически сгенерированного копирования, но это то, чего мы хотели достичь!).

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

17

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

Я думаю, что понятия неизменных объектов в значительной степени находятся в конфликте с передачей по стоимости. Если вы хотите изменить MyIndexЗначит, вам не нужны неизменные объекты? Однако, если вы действительно хотите неизменных объектов, то когда вы пишете index = MyIndex(index.Value + 1); Вы не хотите изменять MyIndex indexхочешь замещать это с другим MyIndex, А это означает совершенно разные понятия. А именно:

std::shared_ptr<MyIndex> index = make_shared<MyIndex>(0);

while(someCondition)
{
DoWork();

index = make_shared<MyIndex>(index.Value + 1);
}

В C ++ это немного странно, но на самом деле именно так работает Java. С этим механизмом MyIndex Можно есть все const члены, и не нуждается ни в конструкторе копирования, ни в назначении копирования. Что правильно, потому что это неизменное. Кроме того, это позволяет нескольким объектам ссылаться на один и тот же MyIndex и знаю, что это будет никогда изменение, которое является большой частью моего понимания неизменных объектов.

Я действительно не знаю, как сохранить все преимущества, перечисленные в http://www.javapractices.com/topic/TopicAction.do?Id=29 без использования стратегии, подобной этой.

class MyClass
{
std::shared_ptr<MyIndex> Index;

void SetMyIndex(const std::shared_ptr<MyIndex>& index)
{
Index = index; // This compiles just fine and does exactly what you want
}
};

Это, вероятно, хорошая идея, чтобы заменить shared_ptr с unique_ptr когда MyIndex «принадлежит» одному чистому месту / указателю.

В C ++ я думаю, что более нормальная вещь — сделать изменчивый структуры, и просто превратить их в const, где это необходимо:

std::shared_ptr<const std::string> one;
one = std::make_shared<const std::string>("HI");
//bam, immutable string "reference"

И поскольку никто не любит накладные расходы, мы просто используем const std::string& или же const MyIndex&и держите право собственности в первую очередь.

3

Хорошо ты Можно замените экземпляр новым экземпляром. Это довольно необычно, в большинстве случаев люди предпочитают использовать оператор присваивания.

Но если вы действительно хотите, чтобы C ++ работал как C #, вы бы сделали это так:

void SetMyIndex(MyIndex index)
{
Index.~MyIndex();            // destroy the old instance (optional)
new (&Index) MyIndex(index); // create a new one in the same location
}

Но нет смысла. Весь совет сделать неизменными структуры C # в любом случае весьма сомнителен и имеет достаточно широкие исключения для управления бульдозером. Большинство случаев, когда C ++ имеет преимущества перед C #, попадают в одно из таких исключений.

Большая часть рекомендаций C # относительно структур состоит в том, чтобы иметь дело с ограничениями на классы значений .NET — они копируются в сыром виде памяти, у них нет деструкторов или финализаторов, и конструктор по умолчанию не вызывается надежно. C ++ не имеет ни одной из этих проблем.

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