読者です 読者をやめる 読者になる 読者になる

syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

C++のmutableの使い道

プログラミングTips C++

(これは2010-10-20に書いた故OCNブログの記事を加筆修正したものです)

C++のmutableはconstメソッド内でも変更可能なフィールドを定義する場合などに使います。mutableはヘタに使うと混乱を招くだけの余計な機能なんですが、こんな感じでスレッドセーフなクラスを実現する際に使うことができます。

template<class T, class Container = std::queue<T> >
class CThreadSafeQueue {
private:
    mutable CCriticalSection m_cs; // MFC の同期オブジェクト。
    Container m_queue;
public:
    void Push(const T& data) {
        CSingleLock lock(&m_cs, TRUE);
        m_queue.push(data);
    }
    bool Pop() {
        CSingleLock lock(&m_cs, TRUE);
        if (m_queue.empty()) {
            return false;
        }
        else {
            m_queue.pop();
            return true;
        }
    }
    bool GetFront(T& info) const {
        CSingleLock lock(&m_cs, TRUE);
        if (m_queue.empty()) {
            return false;
        }
        else {
            info = m_queue.front();
            return true;
        }
    }
    bool GetBack(T& info) const {
        CSingleLock lock(&m_cs, TRUE);
        if (m_queue.empty()) {
            return false;
        }
        else {
            info = m_queue.back();
            return true;
        }
    }
    size_t GetSize() const {
        CSingleLock lock(&m_cs, TRUE);
        return m_queue.size();
    }
};

ここで重要なのはGetFront(), GetBack(), GetSize()のconst修飾子です。const修飾子の付いたメソッド内では、フィールドに対する変更を行なうことができなくなり、これによって「constメソッドであればオブジェクトの内部状態に変更を加えることがない」というコンパイラ保証を付加することができるため、より安全なコードを書くことが可能となるのですが、スレッドセーフなクラスを作るときはメソッド呼び出しをスレッドセーフにするための同期オブジェクト(ロックオブジェクト)を非constで扱う必要があり、衝突することになります。これを回避するために、クラスオブジェクトの本質的なコンテキストとは関係ない同期オブジェクト メンバーはmutableにしておきます。
なお、GetSize()は別にロックしなくてもよさそうに見えるかもしれませんが、ロックなしだと「非const操作中に呼び出してもよい」という保証はstd::queueの内部実装に依存する羽目になるので、自分としてはロックすることを推奨します。単純なsize_tの読み出しだけであれば32bit版でも64bit版でもアトミック操作となるため、ロックなしでも良いと言えなくもないのですが、これがint64_tや構造体型だったりすると話が変わってきます。
ちなみに、C++11のconstメソッドは、constメソッドのみを呼び出す場合に関してはスレッドセーフ性も表明することになります(排他制御を行なうか、ロックフリーであるかは問わない)。記憶にとどめておく必要があるでしょう。