c++ - 如何在不可变的 C++ 对象中实现延迟初始化和缓存?

标签 c++ oop design-patterns immutability lazy-initialization

我不久前已经了解了“优雅对象”原则(请参阅 beautifulobjects.org),并且在 C# 中很容易遵循它们,但现在我正在做一些 c++,不变性给我带来了一些麻烦。我正在寻找有关如何以不可变方式实现延迟初始化和缓存的最佳实践。

延迟初始化和缓存的结合意味着一些数据在构造之后必须存储在对象内部,这需要一定程度的可变行为。 在 C# 中,如果类的所有字段都声明为“只读”,则对象是不可变的。幸运的是,这是一种浅层不变性,因此不可变对象(immutable对象)可以将可变对象封装为只读字段并对其进行操作以实现可变行为,请参阅下面的 C# 示例。

与 C++ 中 C# 的“只读”最接近的是将字段声明为“const”。但是,在 C++ 中,如果将可变对象存储在 const 字段中,则无法像在 C# 中那样直接操作它。尝试在 C++ 中重新创建我的 C# 示例会导致编译时错误,请参阅下面的第二个示例。

有一个解决方法:将每个数组转换为 void 指针并返回到我需要的类型的指针(例如 int 或 bool),然后将该指针视为数组,这样我就可以绕过原始数组的 const 限定。但这很丑陋,看起来像是一些肮脏的黑客行为,并且使代码的可读性比我刚刚删除 const 限定符的要低。

不过,我真的想要那些 const 限定符,它们是一种正式的方式,可以向人们保证该类确实是不可变的,而无需他们阅读整个类的代码。

我想要实现的目标的 C# 示例:

using System;

public sealed class Program
{
    public static void Main()
    {
        var test = 
            new CachedInt(
                ()=>5 // this lambda might aswell be a more expensive calculation, which would justify lazy initialization and caching
            );
        Console.WriteLine(test.Value());
    }
}

public sealed class CachedInt
{
    //note that these are all readonly, this is an immutable class
    private readonly Func<int> source;
    private readonly int[] cache;
    private readonly bool[] hasCached;

    public CachedInt(Func<int> source)
    {
        this.source = source;
        this.cache = new int[1];
        this.hasCached = new bool[1]{false};
    }

    public int Value()
    {
        if(!this.hasCached[0])
        {
            // manipulating mutable objects stored as readonly fields:
            this.cache[0] = this.source.Invoke();
            this.hasCached[0] = true;
        }
        return this.cache[0];
    }
}

导致编译时错误的 C++ 示例:

#include <iostream>
#include <functional>

class CachedInt final
{
private:

    // all const, this is an immutable class
    const std::function<int()> source;
    const int cache[1];
    const bool hasCached[1];

public:

    CachedInt(std::function<int()> source) :
        source(source),
        cache{0},
        hasCached{false}
    {}

    int Value()
    {
        if(!this->hasCached[0])
        {
            // the following two lines obviously don't work due to the const qualification
            this->cache[0] = this->source();
            this->hasCached[0] = true;
        }
        return this->cache[0];
    }
};

int main()
{
    CachedInt test([]()->int{return 5;});
    std::cout << test.Value();
}

丑陋的解决方法:

#include <iostream>
#include <functional>

class CachedInt final
{
private:

    // all const, this is an immutable class
    const std::function<int()> source;
    const int cache[1];
    const bool hasCached[1];

public:

    CachedInt(std::function<int()> source) :
        source(source),
        cache{0},
        hasCached{false}
    {}

    int Value()
    {
        if(!this->hasCached[0])
        {
            // this works but it's ugly. there has to be a better way.
            ((int*)(void*)this->cache)[0] = this->source();
            ((bool*)(void*)this->hasCached)[0] = true;
        }
        return this->cache[0];
    }
};

int main()
{
    CachedInt test([]()->int{return 5;});
    std::cout << test.Value();
}

尝试编译第二个示例时抛出的错误:

 In member function 'int CachedInt::Value()':
24:28: error: assignment of read-only location '((CachedInt*)this)->CachedInt::cache[0]'
25:32: error: assignment of read-only location '((CachedInt*)this)->CachedInt::hasCached[0]'

这个错误不是问题,我知道为什么会抛出它,我只是为了完整性而添加它。

总而言之,我想要一个类进行延迟初始化并缓存结果,但我也希望该类是不可变的。在 C++ 中执行此操作最优雅的方法是什么?

最佳答案

这是一个转移注意力的事情:由于缓存是私有(private)的,所以它是否是const并不重要:你(类成员函数的设计者)完全控制任何改变其值(value)的尝试。

这是一个 C++ 实现,其中值由 const 成员访问

template<typename valueType>
struct cachedFunctionValue {
    cachedFunctionValue(std::function<valueType()> &&f)
    : func(f) {}

    valueType get() const       // constant access to function value
    {
        if(!cache.has_value())
            cache = func();     // works because cache is mutable
        return cache.value();
    }
private:
    const std::function<valueType()> func;
    mutable std::optional<valueType> cache;
};

关于c++ - 如何在不可变的 C++ 对象中实现延迟初始化和缓存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57860900/

相关文章:

c++ - 使用初始化列表来初始化字段和在构造函数中初始化它们有什么区别?

java - 是否有更现代的 OO 版本的 "Let' s Build a Compiler”?

c++ - 自动浏览具有不同数据类型的OpenCV Mat

php - 有没有办法在 php 中定义全局或标准函数,可以从任何地方调用而不包含文件

javascript - 计算另一个对象中匹配的对象

wpf - 如何在 View 模型中使用第三方控件?

c++ - 用于多态处理对象的 oop 设计

iphone - 使用 CLLocationManager 的自定义位置管理器类

c++ - 在 C++ 中创建形状

c++ - 程序在 VS 2008 中运行良好,在程序结束时在 VS 2010 中抛出异常