Как создать неизменяемые структуры в C++?
исходя из фона c#, я привык к практике создания структуры неизменяемыми.
Поэтому, когда я начал программировать на C++, я попытался сделать то же самое с типами, которые я обычно прохожу около по стоимости.
у меня была простая структура, которая представляет пользовательский индекс и просто обертывает целое число. Потому что в C++const
ключевое слово чем-то напоминает readonly
В C#, казалось разумным реализовать мою структуру, как это:
struct MyIndex
{
MyIndex(int value) :
Value(value)
{
}
const int Value;
};
проблема с этим подходом заключается в том, что эта структура ведет себя совершенно иначе, чем непреложный struct в C#. Не только это поле значения MyIndex
переменная не может быть изменена, но и любая существующая MyIndex
переменная (локальная переменная или переменная поля) даже не может быть заменена другой MyIndex
-экземпляр.
поэтому следующие примеры кода не компилируются:
a) использование индекса в качестве переменной цикла.
MyIndex index(0);
while(someCondition)
{
DoWork();
index = MyIndex(index.Value + 1); // This does not compile!
}
b) Изменение поля типа MyIndex
class MyClass
{
MyIndex Index;
void SetMyIndex(MyIndex index)
{
Index = index; // This does not compile!
}
};
эти два образца не компилируются, ошибка сборки одинакова:
ошибка C2582: функция "operator =" недоступна в "MyIndex"
в чем причина этого? Почему переменные не могут быть заменены другим экземпляром, несмотря на то, что они не являются const
? И что означает ошибка сборки exaclty? Что это?!--10-- функция>?
3 ответов
причина этого в том, что в 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
не будут мутированы, если они не будут полностью заменены назначением им нового значения.
Я думаю, что понятия неизменяемых объектов в значительной степени противоречат передаче по значению. Если вы хотите изменить элемент 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
can все 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++ я думаю, что более нормальная вещь, чтобы сделать mutable структуры, и просто сделайте те в 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&
, и держите владение ясным в первую очередь.
Ну, ты can замените экземпляр совершенно новым экземпляром. Это достаточно необычно, в большинстве случаев люди предпочитают использовать оператор присваивания.
но если бы вы действительно хотели, чтобы 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++ не имеет ни одной из этих проблем.