“即时计算”是不是适合用于可变?
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】:事实证明,在我的特殊情况下,有太多不同的缓存值,使得可变的东西变得非常快。 (上面的描述被简化了。)相反,我决定制作两个不同版本的Graph
:ImmutableGraph
和MutableGraph
。 ImmutableGraph
没有添加或删除边和顶点的方法;因此,很少有 ImmutableGraph
应声明为 const
的情况。
【讨论】:
以上是关于“即时计算”是不是适合用于可变?的主要内容,如果未能解决你的问题,请参考以下文章