如何在C++中创建不可变的结构?

Mar*_*cze 18 c++ immutability

来自C#背景,我习惯于使结构不可变的做法.
因此,当我开始使用C++进行编程时,我尝试对通常按值传递的类型执行相同的操作.

我有一个简单的结构,它代表一个自定义索引,只是包装一个整数.因为在C++中const关键字有点类似于readonlyC#,所以实现我的结构似乎是合理的:

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

    const int Value;
};
Run Code Online (Sandbox Code Playgroud)

这种方法的问题是这个结构的行为与C#中的不可变结构完全不同.不仅MyIndex不能修改变量的Value字段,而且任何现有MyIndex变量(局部变量或字段变量)都不能用另一个MyIndex-instance 替换.

所以下面的代码示例不能编译:

a)使用index作为循环变量.

MyIndex index(0);

while(someCondition)
{
    DoWork();

    index = MyIndex(index.Value + 1); // This does not compile!
}
Run Code Online (Sandbox Code Playgroud)

b)修改类型的成员字段 MyIndex

class MyClass
{
    MyIndex Index;

    void SetMyIndex(MyIndex index)
    {
        Index = index; // This does not compile!
    }
};
Run Code Online (Sandbox Code Playgroud)

这两个示例没有编译,构建错误是一样的:

错误C2582:'operator ='函数在'MyIndex'中不可用

这是什么原因?为什么不能将变量替换为另一个实例,尽管它们不是const?构建错误意味着什么是exaclty?那是什么operator =功能?

Mar*_*cze 17

这样做的原因是,在创建变量(局部变量或字段变量)之后的C++中,它不能被另一个实例"替换",只能改变其状态.所以在这一行

index = MyIndex(1);
Run Code Online (Sandbox Code Playgroud)

不是创建新的MyIndex实例(使用其构造函数),而是应该使用其复制赋值运算符更改现有的索引变量.缺少的函数是它缺少的类型的复制赋值运算符,因为如果类型具有const字段,它不会自动生成.operator =MyIndex

它没有产生的原因是它根本无法合理地实现.复制赋值运算符将像这样实现(这在这里不起作用):

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

    return *this;
}
Run Code Online (Sandbox Code Playgroud)

因此,如果我们希望我们的结构实际上是不可变的,但行为类似于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;
};
Run Code Online (Sandbox Code Playgroud)

严格来说,MyIndex结构不是不可变的,但它的状态不能用从外部访问的任何东西来改变(除了它自动生成的复制赋值操作符,但这就是我们想要实现的!).

现在上面的代码示例编译正确,我们可以确定我们的类型变量MyIndex不会被改变,除非它们被赋予新值完全替换.