如何在c++中创建不可变结构体
How to create immutable structs in C++?
来自c#背景,我习惯于使结构不可变。
因此,当我开始用c++编程时,我尝试对我通常通过值传递的类型做同样的事情。
我有一个简单的结构体,它表示一个自定义索引,并简单地包装了一个整数。因为在c++中const
关键字有点类似于c#中的readonly
,所以像这样实现我的结构似乎是合理的:
struct MyIndex
{
MyIndex(int value) :
Value(value)
{
}
const int Value;
};
这种方法的问题是,这个结构体的行为与c#中的不可变结构体完全不同。不仅MyIndex
变量的Value字段不能修改,而且任何现有的MyIndex
变量(局部变量或字段变量)甚至不能被另一个MyIndex
实例替换。
所以下面的代码示例不能编译:
a)使用index作为循环变量
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
?构建错误究竟意味着什么?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
}
};
当MyIndex
被一个明确的位置/指针"拥有"时,将shared_ptr
替换为unique_ptr
可能是一个好主意。
在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++没有这些问题。
- 正在寻找C++不可变的hashset/hashmap
- 在C++中,如何创建包含可变模板对象的异构向量?
- 具有常量属性的不可变类
- 为什么将函数传递给内核会导致数据变得不可变?
- 现代c++编译器会优化不可变的临时变量吗
- C++中的不可变列表
- C++ 创建不带默认构造函数的对象数组
- 如何将地图作为不可变地图传递?
- 不可变的全局对象应该声明为"const my_result_t BLAH"还是"extern const my_result_t BLAH;"?
- 创建不带指针的可变大小数组
- 可变长度数组:如何在C++中创建具有可变大小的缓冲区
- 向不可变纹理存储提供数据
- C++创建具有可变项目数的结构的方法
- 在C++中创建不可变且高效的类的惯用方法
- 在C++中创建不可变对象的更好方法
- 创建不可变的 CFArray 对象 - Apple 示例不起作用
- 如何在c++中创建不可变结构体
- 避免在c++中创建新的不可变实例的样板文件
- 如何在类中创建不可变的静态公共对象?
- OpenGL ES 3.1 -无法用glTexImage2D创建不可变纹理