CERT.CTR.MUTABLE_PREDICATE

述語関数オブジェクトは、自身の状態を変更してはなりません。

このチェッカーは、モダンエンジンにのみ適用されます。

CERT.CTR.MUTABLE_PREDICATE チェッカーは、C++ 標準ライブラリの操作で使用されている間に自身の状態を変更する、述語関数オブジェクトおよびラムダ式の述語を検出します。これらの操作では述語がコピーされる場合があるため、状態を変更すると、予期しない結果や一貫性のない結果が生じるおそれがあります。

脆弱性とリスク

述語が自身の状態を変更すると、同じ操作中にその述語の別々のコピーの状態が異なるものになる可能性があります。これにより、予測やデバッグが困難な、不正なフィルタリング、順序付け、または照合動作が発生するおそれがあります。

軽減と防止

自身の状態を変更しない述語を優先します。述語が状態を保持する必要がある場合は、std::ref または std::cref などを使用して名前付きの述語を参照として渡します。これにより、操作が意図しないコピーを使用しないようにします。

脆弱コード例

コピー
#include <algorithm>
#include <vector>

class RemoveNth {
    size_t calls;
    size_t target;

public:
    explicit RemoveNth(size_t target) : calls(0), target(target) {}

    bool operator()(const int &) {
        return ++calls == target;
    }
};

void filter_values() {
    std::vector<int> values{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    values.erase(std::remove_if(values.begin(), values.end(), RemoveNth(3)), values.end());
}

この非準拠例では、述語は実行されるたびに内部状態を更新します。ライブラリ操作が述語をコピーすると、それらのコピーがそれぞれ独立してカウンターを進め、誤った要素を削除したり、一致させたりする可能性があります。

修正コード例

コピー
#include <algorithm>
#include <functional>
#include <vector>

class RemoveNth {
    size_t calls;
    size_t target;

public:
    explicit RemoveNth(size_t target) : calls(0), target(target) {}

    bool operator()(const int &) {
        return ++calls == target;
    }
};

void filter_values() {
    std::vector<int> values{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    RemoveNth predicate(3);
    values.erase(std::remove_if(values.begin(), values.end(), std::ref(predicate)), values.end());
}

この準拠例では、名前付きの述語を参照として渡すことで、ライブラリ操作が意図しないコピーを使用するのではなく、常に同じ述語の状態を使用するようにしています。

外部参考資料