我担心我违反了mutable
的合同,我将其用于在异步执行按需请求的数据模型中缓存信息。数据模型恰好是Qt,尽管这不是一个特别重要的事实。
class MyDataModel : public QAbstractItemModel
{
public:
QVariant data( const QModelIndex & index, int role ) const override;
private:
void SignalRowDataUpdated( int row ) const;
mutable SimpleRowCache mCache;
};
调用data()
时,我会检查缓存以查看是否有此缓存。如果没有,我立即返回空数据(以避免阻止UI),并向API发送异步请求以填充缓存。由于data()
必须是const,因此mCache
是可变的。 data()
的内容看起来像这样:
RowData row_data = mCache.Get( row );
if( !row_data )
{
// Store empty data in cache, to avoid repeated API requests
mCache.Set( row, RowData() );
// Invoke API with a lambda to deliver async result. Note: 'this' is const
auto data_callback = [this, row]( RowData data )
{
mCache.Set( row, std::move(data) );
SignalRowDataUpdated( row );
};
DataApi::GetRowData( row, data_callback );
return QVariant::Invalid;
}
return row_data[ column ];
我担心的是这里违反了数据模型对象的逻辑const :为某个索引调用data()
可以直接导致将来调用具有返回不同值的相同参数。
这是一个坏主意吗?是否有“正确”做到这一点的共同模式/范式?
脚注:我与SignalRowDataUpdated()
有类似的问题。这实际上是发出Qt信号的包装器:emit dataChanged( from, to )
,这是一个非const调用。我通过在构造时捕获lambda中的this
来处理那个,允许我从const函数调用非const方法。我不为此感到骄傲=(
答案 0 :(得分:3)
首先,今天的const成员函数与逻辑const 无关。 关于线程安全(参见https://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/)。
主要的是,如果你创建成员函数 const ,你应该保证如果从多个线程调用它,就不会有竞争条件。
如果你不喜欢你的类定义中的 mutable 缓存,请使用间接,在你的类定义中创建一个指向缓存的智能指针。您不需要 mutable ,编译器不会对您的函数 const 感到不安:)
答案 1 :(得分:0)
可以定义MyDataModel::data()
的合同,以便函数可以返回值,并且不会阻止。如果它返回QVariant::Invalid
,它必须在将来调用回调。
第一次查询data()
时类启动异步请求的事实是类内部的一部分,data()
不会以外部可见的方式修改对象:{ {1}}仍然可能会或可能不会立即为下次通话返回值。
问题是,如果data()
的用户依赖于MyDataModel
将返回值immediatley缓存后的事实,并使用回调传递它以前的时间。因为它在逻辑上是const,所以这些行为中的哪一个需要保持未定义,即调用者必须始终期望两者。
如果函数data()
总是使用回调传递值,并且永远不返回值,则接口可能更干净。如果数据在data()
中,它将立即调用回调。然后合同将是mCache
(可能重命名为data()
或类似)导致将来使用请求的值调用回调。它仍然可以是async_retrieve()
,因为对象不会与外部不同。
也许是另一个函数const
,其中调用者总是通过返回值同步接收值,但如果该值尚未缓存,则允许阻塞。
通过此方式,调用者可以决定如何接收值,而sync_retrieve()
可以在内部进行缓存以提高效率。
答案 2 :(得分:0)
看起来真正的问题是您的数据(...)方法可能不是线程安全的,除非您确定只能从一个线程调用数据(...)(UI线程可能?我不熟悉Qt。)
假设有两个线程同时调用数据(...):
1)两个线程都可能执行if(!row_data)中的代码,你需要添加一个锁来同步这个并仔细检查mCache.Get(row)以查看它是否'如果(!row_data)
,则为null2)之后,第一次调用数据(...)引起的SignalRowDataUpdated调用也可能在第二次调用QVariant返回的数据(...)之前执行::无效。我想这不会是你想要的。在这种情况下,您还需要同步数据(...)和SignalRowDataUpdated。