如何在程序代码中使用不带 operator=() 的不可变对象
How to use immutable object without operator=() in procedural code
给定一个不可变的 C++ 对象(它的成员是 const
,这意味着它的 operator=()
方法不存在),你如何实现这个简单的过程模式(这需要 Object::operator=()
存在):
Object someObject {17};
// ...
if (...) {
someObject = Object {42};
}
// ...
use(someObject);
在这种情况下,我的模式是将初始化代码提取到一个函数中:
Object ini(...){
if(...) {
return Object{42};
}
return Object{17};
}
.....
Object someObject=ini(...);// copy constructor used (or not, because of RVO)
use(someObject);
如果初始化很简单,您可以使用:
Object someObject = ...? Object{42} : Object{17};
这与声明您的 o
变量常量没有太大区别。
如果使用 someObject=17
然后用 someObject=42
替换 - 它只是破坏了通过声明一些成员 const 所追求的良好意图。
有两种选择:
- 声明一些成员
const
不是一个好主意 - 它可以撤消并可以添加赋值运算符。
- 使用
Object
,因为它本来就是要使用的。
不应该轻率地做的事情:用 pointers/references 做一些技巧 - 它只会让你的代码更复杂。如果需要,最好使用新变量:
Object someObject {17};
// ...
Object newSomeObject = ... ? Object {42} : someObject {17};
use(newSomeObject);
如果旧对象的复制可能是一个性能问题,代码可以这样重构,即
use(need42(..) ? create42() : object17);
无需复制数据即可使用。此解决方案假定 use
使用其参数的 const 引用或参数按值传递。
在我看来,不可变对象的每次更改都应该产生一个新对象,否则,可能会发生以下情况:
ImmutableObject obj(1);
ImmutableObject &ref=obj;//ref.member=1
...
obj=ImmutableObject(2);
//ref.member!=1, that is puzzling, I assumed ref to be immutable!
现在,您的对象的用户(通过 ref
)会因为对象被更改而生气!不变性的全部要点是您可以推断,值 永远不会 改变。如果它们可以改变,那么首先使用 "immutables" 并没有那么多优势。
解决方法是使用 shared_ptr。
shared_ptr<Object> someObject(new Object(17));
shared_ptr<Object> anotherObject(new Object(42));
// ...
if (...) {
someObject = anotherObject;
}
use(someObject);
其他答案在您的初始化逻辑可能很简单时有效,但如果您正在整理一些意大利面条式代码,这个可能有帮助。
如果不是,请借鉴 java(他们可能没有发明它,但我看到 java 程序员最常使用它)- 构建器模式。在 C++
.
中有两种可能的实现方式
#include <string>
class ImmutableClass {
public:
ImmutableClass(int a, int b, std::string c) : a_(a), b_(b), c_(c) {}
// Getters...
private:
ImmutableClass& operator=(const ImmutableClass&) = delete;
const int& GetA() {return a_;}
const int& GetB() {return b_;}
const std::string& GetC() {return c_;}
const int a_;
const int b_;
const std::string c_;
};
struct ImmutableClassBuilderExampleOne {
public:
// Note the default initialization to avoid undefined behavior.
int a = 0;
int b = 0;
std::string c;
};
// Less boilerplate, less encapsulation, if that's your thing.
ImmutableClass BuildImmutableClass(const ImmutableClassBuilderExampleOne& icb) {
return ImmutableClass(icb.a, icb.b, icb.c);
}
// More boilerplate, more encapsulation, can be "tidied" with macros.
class ImmutableClassBuilderExampleTwo {
public:
const ImmutableClass build() {
return ImmutableClass(a_, b_, c_);
}
ImmutableClassBuilderExampleTwo& setA(const int a) {
a_ = a;
return *this;
}
ImmutableClassBuilderExampleTwo& setB(const int b) {
b_ = b;
return *this;
}
ImmutableClassBuilderExampleTwo& setC(const std::string& c) {
c_ = c;
return *this;
}
private:
// Note the default initialization to avoid undefined behavior.
int a_ = 0;
int b_ = 0;
std::string c_;
};
给定一个不可变的 C++ 对象(它的成员是 const
,这意味着它的 operator=()
方法不存在),你如何实现这个简单的过程模式(这需要 Object::operator=()
存在):
Object someObject {17};
// ...
if (...) {
someObject = Object {42};
}
// ...
use(someObject);
在这种情况下,我的模式是将初始化代码提取到一个函数中:
Object ini(...){
if(...) {
return Object{42};
}
return Object{17};
}
.....
Object someObject=ini(...);// copy constructor used (or not, because of RVO)
use(someObject);
如果初始化很简单,您可以使用:
Object someObject = ...? Object{42} : Object{17};
这与声明您的 o
变量常量没有太大区别。
如果使用 someObject=17
然后用 someObject=42
替换 - 它只是破坏了通过声明一些成员 const 所追求的良好意图。
有两种选择:
- 声明一些成员
const
不是一个好主意 - 它可以撤消并可以添加赋值运算符。 - 使用
Object
,因为它本来就是要使用的。
不应该轻率地做的事情:用 pointers/references 做一些技巧 - 它只会让你的代码更复杂。如果需要,最好使用新变量:
Object someObject {17};
// ...
Object newSomeObject = ... ? Object {42} : someObject {17};
use(newSomeObject);
如果旧对象的复制可能是一个性能问题,代码可以这样重构,即
use(need42(..) ? create42() : object17);
无需复制数据即可使用。此解决方案假定 use
使用其参数的 const 引用或参数按值传递。
在我看来,不可变对象的每次更改都应该产生一个新对象,否则,可能会发生以下情况:
ImmutableObject obj(1);
ImmutableObject &ref=obj;//ref.member=1
...
obj=ImmutableObject(2);
//ref.member!=1, that is puzzling, I assumed ref to be immutable!
现在,您的对象的用户(通过 ref
)会因为对象被更改而生气!不变性的全部要点是您可以推断,值 永远不会 改变。如果它们可以改变,那么首先使用 "immutables" 并没有那么多优势。
解决方法是使用 shared_ptr。
shared_ptr<Object> someObject(new Object(17));
shared_ptr<Object> anotherObject(new Object(42));
// ...
if (...) {
someObject = anotherObject;
}
use(someObject);
其他答案在您的初始化逻辑可能很简单时有效,但如果您正在整理一些意大利面条式代码,这个可能有帮助。
如果不是,请借鉴 java(他们可能没有发明它,但我看到 java 程序员最常使用它)- 构建器模式。在 C++
.
#include <string>
class ImmutableClass {
public:
ImmutableClass(int a, int b, std::string c) : a_(a), b_(b), c_(c) {}
// Getters...
private:
ImmutableClass& operator=(const ImmutableClass&) = delete;
const int& GetA() {return a_;}
const int& GetB() {return b_;}
const std::string& GetC() {return c_;}
const int a_;
const int b_;
const std::string c_;
};
struct ImmutableClassBuilderExampleOne {
public:
// Note the default initialization to avoid undefined behavior.
int a = 0;
int b = 0;
std::string c;
};
// Less boilerplate, less encapsulation, if that's your thing.
ImmutableClass BuildImmutableClass(const ImmutableClassBuilderExampleOne& icb) {
return ImmutableClass(icb.a, icb.b, icb.c);
}
// More boilerplate, more encapsulation, can be "tidied" with macros.
class ImmutableClassBuilderExampleTwo {
public:
const ImmutableClass build() {
return ImmutableClass(a_, b_, c_);
}
ImmutableClassBuilderExampleTwo& setA(const int a) {
a_ = a;
return *this;
}
ImmutableClassBuilderExampleTwo& setB(const int b) {
b_ = b;
return *this;
}
ImmutableClassBuilderExampleTwo& setC(const std::string& c) {
c_ = c;
return *this;
}
private:
// Note the default initialization to avoid undefined behavior.
int a_ = 0;
int b_ = 0;
std::string c_;
};