“即时计算”是不是适合用于可变?

Posted

技术标签:

【中文标题】“即时计算”是不是适合用于可变?【英文标题】:Is "just-in-time calculation" an appropriate use for mutable?“即时计算”是否适合用于可变? 【发布时间】:2017-01-01 17:36:22 【问题描述】:

图可以表示为邻接矩阵或邻接列表。我的Graph 对象将图形表示为邻接矩阵。出于性能原因,除非请求,否则我不计算邻接表;但是,一旦请求,我想保留该列表(以避免重新构建它)。

是否适合制作邻接列表mutable,以便用户可以为其他const Graph 对象生成邻接列表?我问是因为我不相信构建邻接矩阵将被视为“物理”而不是“逻辑”更改Graph 的状态。我还有一个adjacencyListBuilt 方法,所以邻接列表的构建不是“不可见的”(参见https://isocpp.org/wiki/faq/const-correctness#mutable-data-members)。

如果我理解正确,声明 adjacencyList 实例变量 mutable 将允许 any 方法对其进行更新。有没有办法只有buildAdjacencyList 方法才能修改const 对象上的adjacencyList 实例变量?

【问题讨论】:

最后一个问题 - 好吧,只是不要以任何其他方法触摸它。你是课程的作者,没有人会在buildAdjacencyList之外修改adjacencyList adjacencyListBuilt 的目的是什么?调用者应该如何使用这些信息?这听起来像是暴露了一个与调用者无关的实现细节。把它拿出来,使用mutable作为getAdjacencyList()结果缓存的成员变得非常合理。 adjacencyListBuilt 仅在 assert 语句中使用。我不是在每次调用getAdjacencyList 时检查列表是否已构建,而是依靠程序员在需要时请求构建它。 (是的,我知道删除此检查的好处非常小。) 好吧,如果你喜欢 a) const Graph 可用的设计,但是 b) 它的邻接列表是在第一个请求时动态计算并在之后缓存的,那么我会摆脱公共buildAdjacencyList() 并让getAdjacencyList() 在第一次调用时计算并缓存它,并在后续调用中返回缓存的结果。如果在测量了这种安排的性能之后,您得出结论认为getAdjacencyList() 中的“已构建列表”检查成本太高,则将buildAdjacencyList() 重新公开为const 方法。这很奇怪,但有时我们不得不牺牲纯度来换取性能。 【参考方案1】:

使用mutable 缓存从内部成员计算的结果是合适的。请注意,它可能会破坏线程安全。

但我也会考虑一个单独的类或函数,它对 const 对象执行计算。

【讨论】:

【参考方案2】:

有没有办法只有 buildAdjacencyList 方法才能修改 const 对象上的 adjacencyList 实例变量?

当然,使用嵌套的私有成员和friend

class myGraph;
void buildAdjacencyListImpl(const myGraph&);

class myGraph

    class myAdjacencyListCache
    
         mutable realAdjacencyList cached_list;

         friend void buildAdjacencyListImpl(const myGraph&);
     adj_list;
    friend void buildAdjacencyListImpl(const myGraph&);

    void buildAdjacencyList() const  buildAdjacencyListImpl(*this); 
;

void buildAdjacencyListImpl( const myGraph& g )

    realAdjacencyList& listToBuild = g.adj_list.list_cache;
    // it isn't const, and can be modified

【讨论】:

【参考方案3】:

“即时计算”正是mutable 的发明目的,因此是的,它是一个合适的用途。请注意,该接口不会专门要求构建它;您只需请求访问它,该类会注意到它尚未构建,并在“幕后”构建它。因此逻辑状态不会受到影响。

如果您正在考虑请求构建的函数,然后在尚未请求构建时调用无效的函数或访问函数集,那么您做错了。请注意,问题将出在 interface 而不是实现上;因此无论你是否使用mutable 实现它都是错误的。

关于如何进一步保护它免受其他方法影响的问题:如果我理解正确,对邻接列表只应该做两件事:要么应该从当前的邻接矩阵计算,要么应作废。因此,我建议将其封装到一个单独的类(Graph 类私有)中,该类封装了可变成员(并且本身用作非可变成员变量)并且只提供两个操作:(1)访问事件矩阵(const 函数,如果尚未计算,则计算列表)和(2)使列表无效(非 const,因为只有对图形的更改才能使列表无效)。这样,Graph 类的任何 const 成员函数都不能修改列表。

【讨论】:

【参考方案4】:

事实证明,在我的特殊情况下,有太多不同的缓存值,使得可变的东西变得非常快。 (上面的描述被简化了。)相反,我决定制作两个不同版本的GraphImmutableGraphMutableGraphImmutableGraph 没有添加或删除边和顶点的方法;因此,很少有 ImmutableGraph 应声明为 const 的情况。

【讨论】:

以上是关于“即时计算”是不是适合用于可变?的主要内容,如果未能解决你的问题,请参考以下文章

scala splat 可以用于任何不是可变参数的东西吗?

使 NSView 具有适合内容的固定宽度和可变高度

用于可变长度参数数组的 PHPDoc

我可以告诉 Perl 一些数据是不可变的以加快速度吗?

不可变类

python中元组可变吗