如何在过程代码中使用不带operator =()的不可变对象

时间:2017-09-29 04:50:28

标签: c++ const immutability

给定一个不可变的C ++对象(其成员为const,这意味着它的operator=()方法不存在),如何实现这个简单的过程模式(需要Object::operator=()来实现存在):

Object someObject {17};

// ...

if (...) {
   someObject = Object {42};
}

// ...

use(someObject);

3 个答案:

答案 0 :(得分:2)

解决方法是使用shared_ptr。

shared_ptr<Object> someObject(new  Object(17));
shared_ptr<Object> anotherObject(new Object(42));
// ...

if (...) {

   someObject = anotherObject;
}

use(someObject);

答案 1 :(得分:1)

在这种情况下我的模式是将初始化代码提取到函数中:

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 - 变量const。

并没有什么不同

如果someObject=17被使用,然后被someObject=42取代 - 它只会破坏通过声明某些成员const所追求的良好意图。

有两种选择:

  1. 声明某些成员const并不是一个好主意 - 它可以撤消,并且可以添加一个分配运算符。
  2. 使用Object,因为它意味着要使用。
  3. 不应该轻易做什么:使用指针/引用制作一些技巧 - 它只会让你的代码变得更加复杂。如果需要,最好使用新变量:

    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)会因为对象被更改而烦恼!不变性的全部意义在于,您可以推断,永远不会更改的值。如果他们可以改变,那么使用&#34; immutables&#34;并没有太多的优势。首先。

答案 2 :(得分:0)

当您的初始化逻辑可能很简单时,其他答案会起作用,但如果您正在解开一些意大利面条代码,那么可能会帮助。

如果不是,请从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_;
};