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

Posted

技术标签:

【中文标题】如何使用分支逻辑构造不可变数据类型的实例?【英文标题】:How can I construct an instance of an immutable data type using branching logic? 【发布时间】:2017-02-09 17:00:57 【问题描述】:

我正在使用 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);

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

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

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

【问题讨论】:

为什么不简单地将const int x; 隐藏为私有成员int x; 并且不允许通过任何公共/受保护的成员函数进行更改? @tobi303 我不想在构造后对其进行变异,但我希望构造依赖于我的分支逻辑。或者,我想我可以在我的分支逻辑中改变构造函数参数,然后在最后的一行中构造一个实例。 在不改变界面的情况下,std::uniqe_ptr&lt;Immutable&gt; 解决方案看起来最直接的 IMO。 @Ryan 除了机制之外,您能否提供一个更好的具体用例示例(edit 在您的问题中)。 @Dan 不是。但解释了我在第一条评论中的意思。 【参考方案1】:

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

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

【讨论】:

“功能”也是我立即想到的。 @πάνταῥεῖ ?我不明白参考文献 @Martin Benjamin == Brain,Martin == Pinky。没有提到的冒犯。【参考方案2】:

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

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

【讨论】:

基本上是boost::optional(或c++17中的std::optional)。 @BenjaminLindley 很好,我没有意识到std::optional 即将到来(为什么花了这么长时间?)。是的,基本上就是这样。

以上是关于如何使用分支逻辑构造不可变数据类型的实例?的主要内容,如果未能解决你的问题,请参考以下文章

python实例可变数据类型与不可变数据类型

NPE,同时访问实例化和构造函数配置的不可变对象的原始类型(双精度)。 (不涉及自动装箱或反射)

java 如何方法中参数传入class A(class类型可变),返回结果是个A类型的实例,

java中的不可变类型的探究

软件构造复习内容(10)---并发

软件构造课程提纲