【问题标题】: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 方法。这很奇怪,但有时我们不得不牺牲纯度来换取性能。

标签: c++ mutable const-correctness


【解决方案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 的情况。

        【讨论】:

          猜你喜欢
          • 2014-07-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-06-16
          • 1970-01-01
          • 1970-01-01
          • 2013-04-18
          • 1970-01-01
          相关资源
          最近更新 更多