jeudi 24 novembre 2016

Using 'mutable' for asynchronously-populated cache in a const method

I'm concerned that I'm breaking the contract of mutable which I use for caching information in a data model that performs on-demand requests asynchronously. The data model happens to be Qt, although that's not a particularly important fact.

class MyDataModel : public QAbstractItemModel
{
public:
    QVariant data( const QModelIndex & index, int role ) const override;

private:
    void SignalRowDataUpdated( int row ) const;
    mutable SimpleRowCache mCache;
};

When data() is called, I check the cache to see if we have it. If not, I return empty data immediately (to avoid blocking the UI) and also send an asynchronous request to the API to populate the cache. Since data() must be const, this requires that mCache is mutable. The guts of data() looks like this:

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 ];

My concern is that the data model object's logical constness is being violated here: Calling data() for some index can directly result in a future call with the same parameters returning a different value.

Is this A Bad Idea? And is there a common pattern / paradigm for doing it "correctly"?


Footnote: I have a similar issue with SignalRowDataUpdated(). This is actually a wrapper around emitting a Qt signal: emit dataChanged( from, to ), which is a non-const call. I've handled that one by capturing this in a lambda at construction time allowing me to call the non-const method from a const function. I don't feel proud of this =(

Aucun commentaire:

Enregistrer un commentaire