在 const 方法中对异步填充的缓存使用“可变”

pad*_*ddy 5 c++ qt c++11

我担心我违反了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;
};
Run Code Online (Sandbox Code Playgroud)

data()被调用时,我检查缓存以查看是否有缓存。如果没有,我会立即返回空数据(以避免阻塞 UI)并向 API 发送异步请求以填充缓存。由于data()must 是 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 ];
Run Code Online (Sandbox Code Playgroud)

我担心的是这里违反了数据模型对象的逻辑常量:调用data()某个索引会直接导致将来使用相同参数的调用返回不同的值。

这是一个坏主意吗?是否有一个共同的模式/范式可以“正确”地做到这一点?


脚注:我有类似的问题SignalRowDataUpdated()。这实际上是围绕发出 Qt 信号的包装:emit dataChanged( from, to ),这是一个非常量调用。我通过this在构造时捕获lambda来处理这个问题,允许我从 const 函数调用非常量方法。我不为此感到自豪=(

120*_*arm 4

来自 C++ 语言标准中的 [class.this] 部分,

如果成员函数声明为 const,则 this 的类型为 const X*

所以改变了;const的类型 this(我发现)没有关于类的“逻辑常量”的信息。既然这是一个指向 的 const 指针MyDataModel,你能用它做什么呢?[decl.type.cv] 告诉我们

除了声明为可变的任何类成员都可以修改之外,任何在其生命周期 (3.8) 期间修改 const 对象的尝试都会导致未定义的行为。

由于已声明,因此允许对其mCache内部进行更改。data()mutable

不要求 const 成员函数在使用相同参数调用时必须返回相同的值,因为内部状态可以通过更新可变成员或干预对非常量成员函数的调用来更改。

总之,只要您的调用代码可以处理三个不同的返回值(如果当前正在加载数据,则为QVariant::Invalid空对象,以及加载的行),您就不应该对成员函数声明有任何问题。RowData

当您将新加载的数据移动到缓存中时,回调中可能会出现问题。如果另一个线程尝试同时加载该行的数据,它可以获得RowData一个空行和已加载行混合的对象。

在 中SignalRowDataUpdated,为什么要通过 lambda 进行间接寻址,而不是执行const_caston this