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

How can I construct an instance of an immutable data type using branching logic?

我正在用 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
...

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

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);

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

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

获得不变性好处的正确技术是什么?

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

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()) {}
};

unique_ptr 几乎是合适的,除了它进行堆分配。不过我们可以制作自己的智能指针容器class。

#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; }
};
onstack<Immutable> x;
if (a)
    x(1);
else if (b)
    x(2);
else
    x(3);

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