Исходя из 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 =
функционировать?
Причина этого заключается в том, что в 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
Можно есть все 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&
и держите право собственности в первую очередь.
Хорошо ты Можно замените экземпляр новым экземпляром. Это довольно необычно, в большинстве случаев люди предпочитают использовать оператор присваивания.
Но если вы действительно хотите, чтобы 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 ++ не имеет ни одной из этих проблем.