Как создать неизменяемые структуры в 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++ не имеет ни одной из этих проблем.