如何在程序代码中使用不带 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 所追求的良好意图。

有两种选择:

  1. 声明一些成员 const 不是一个好主意 - 它可以撤消并可以添加赋值运算符。
  2. 使用 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_;
};