如何使用分支逻辑构造不可变数据类型的实例?

时间:2017-02-09 17:00:57

标签: c++ immutability

我使用C ++进行编程并尝试使用不可变数据类型,作为一种学习新东西的方法,也因为我被告知使用不可变数据类型可以更容易推理代码的影响,因为你知道一旦构建了一个实例,它就无法改变。

我经常想使用分支逻辑创建数据实例。例如:

int x = 0;
if (a)
{
    x = 1;
}
else
{
    if (b)
    {
        x = 2;
    }
    else
    {
        x = 3;
    }
}
DoSomething(x);

但是,如果我的数据类型是不可变的,那么该代码将无法编译,因为没有复制赋值运算符:

struct Immutable
{
public:
    const int x;

    Immutable(const int x)
    : x(x)
    {}
}

Immutable x(0);
if (a)
{
    x = Immutable(1); // Compiler error
...

我可以想到两种可能的解决方案。首先,我可以使用三元运算符根据条件构建我的数据:

Immutable x = a ? Immutable(1) : (b ? Immutable(2) : Immutable(3));

但这会很快导致复杂的语法。

或者,我可以使用std::unique_ptr

std::unique_ptr<Immutable> x = nullptr;
if (a)
{
    x = std::unique_ptr<Immutable>(new Immutable(1));
}
else
{
    if (b)
    {
        x = std::unique_ptr<Immutable>(new Immutable(2));
    }
    else
    {
        x = std::unique_ptr<Immutable>(new Immutable(3));
    }
}
DoSomething(*x);

但在我看来,这似乎可能会否定首先使用不可变数据的好处。

最后,可能是我尝试做的事情没有意义,我应该使用可变数据类型。

用什么方法来获得不变性的好处?

2 个答案:

答案 0 :(得分:7)

只需将参数创建为独立变量即可。在您提交的情况下:

int n;
if (whatever) {
    n = 0;
} else {
    n = 1;
}

Immutable x(n);

如果需要在一行中初始化它(例如,在构造函数的初始化列表中),那么只需将逻辑放在一个函数中。

Immutable foo() {
    int n;
    if (whatever) {
        n = 0;
    } else {
        n = 1;
    }

    return Immutable(n);
}

struct Thingy {
    Immutable x;
    Thingy() :x(foo()) {}
};

答案 1 :(得分:2)

id几乎是合适的,除了它进行堆分配。我们可以创建自己的智能指针容器类。

unique_ptr
#include <memory>
#include <utility>
template<class T> struct onstack {
    __attribute__((__aligned__(__alignof__(T))))
    char buffer[sizeof(T)];
    bool initialized;
    onstack() : initialized(false) {}
    ~onstack() { if (initialized) (*this)->~T(); initialized = false; }
    template<class... Args> void operator()(Args&&... args) {
        if (initialized) (*this)->~T();
        initialized = false;
        new (buffer) T(std::forward<Args>(args)...);
        initialized = true;
    }
    operator boolean() { return initialized; }
    T& operator*() { return reinterpret_cast<T&>(buffer); }
    T* operator->() { return initialized ? &**this : nullptr; }
};

如果不同的分支采用不同的参数,这可能很有用。