いいえ。ただし、ほとんどの場合、そうなります。
const
「スレッドセーフ」およびmutable
「(すでに)スレッドセーフ」と考えることは有用const
ですが、それでも基本的に「この値を変更しない」という約束の概念と結びついています。いつもそうなるでしょう。
私は長い考えを持っているので、我慢してください。
私自身のプログラミングでは、const
どこにでも置いています。私が価値を持っているなら、私がしたいと言わない限り、それを変えるのは悪いことです。const-objectを意図的に変更しようとすると、コンパイル時エラーが発生します(修正が簡単で、出荷可能な結果はありません!)。非constオブジェクトを誤って変更すると、ランタイムプログラミングエラー、コンパイル済みアプリケーションのバグ、および頭痛の種が発生します。したがって、前者の側で誤りを犯し、物事を維持する方が良いconst
です。
例えば:
bool is_even(const unsigned x)
{
return (x % 2) == 0;
}
bool is_prime(const unsigned x)
{
return /* left as an exercise for the reader */;
}
template <typename Iterator>
void print_special_numbers(const Iterator first, const Iterator last)
{
for (auto iter = first; iter != last; ++iter)
{
const auto& x = *iter;
const bool isEven = is_even(x);
const bool isPrime = is_prime(x);
if (isEven && isPrime)
std::cout << "Special number! " << x << std::endl;
}
}
のパラメータタイプis_even
とis_prime
マークが付いているのはなぜconst
ですか?実装の観点から、私がテストしている数を変更することはエラーになるからです!なぜconst auto& x
ですか?私はその値を変更するつもりはないので、変更した場合はコンパイラーに怒鳴りつけてもらいたいからです。isEven
と同じisPrime
:このテストの結果は変わらないはずなので、強制します。
もちろん、メンバー関数は、フォームのタイプconst
を与えるための単なる方法です。「メンバーの一部を変更すると、実装エラーになります」と書かれています。 this
const T*
mutable
「私を除いて」と言います。これが「論理的にconst」の「古い」概念の由来です。彼が与えた一般的なユースケースを考えてみましょう:ミューテックスメンバー。プログラムが正しいことを確認するには、このミューテックスをロックする必要があるため、プログラムを変更する必要があります。ただし、他のメンバーを変更するとエラーになるため、関数を非定数にする必要はありません。したがって、それを作成しconst
、ミューテックスをとしてマークしmutable
ます。
これはスレッドセーフとは関係ありません。
新しい定義が上記の古いアイデアに取って代わると言うのは一歩遠すぎると思います。それらは、スレッドセーフという別の観点からそれを補完するだけです。
これで、Herbは、const
関数がある場合、標準ライブラリで安全に使用できるように、それらはスレッドセーフである必要があることを示しています。これの当然の結果として、関数mutable
から変更可能であるため、すでにスレッドセーフであるメンバーとして実際にマークする必要がある唯一のメンバーは次のとおりです。const
struct foo
{
void act() const
{
mNotThreadSafe = "oh crap! const meant I would be thread-safe!";
}
mutable std::string mNotThreadSafe;
};
さて、スレッドセーフなものはとしてマークできるmutable
ことを私たちは知っています、あなたは尋ねます:それらはそうあるべきですか?
両方の見方を同時に考えなければならないと思います。ハーブの新しい観点からは、そうです。これらはスレッドセーフであるため、関数の定数に拘束される必要はありません。しかし、彼らがの制約から安全に免除されることができるconst
という理由だけで、彼らがそうしなければならないという意味ではありません。私はまだ考慮する必要があります:そのメンバーを変更した場合、実装のエラーになりますか?もしそうなら、それはそうである必要はありませんmutable
!
ここには粒度の問題があります。一部の関数は、メンバーになる予定のmutable
メンバーを変更する必要がある場合がありますが、そうでない関数もあります。これは、一部の関数だけに友達のようなアクセス権を持たせたいようなものですが、クラス全体を友達にすることしかできません。(これは言語設計の問題です。)
この場合、の側で誤りを犯す必要がありmutable
ます。
const_cast
彼が例を挙げたとき、ハーブはそれが安全であると宣言したとき、ほんの少し緩すぎました。検討:
struct foo
{
void act() const
{
const_cast<unsigned&>(counter)++;
}
unsigned counter;
};
foo
これは、オブジェクト自体がconst
次の場合を除いて、ほとんどの状況で安全です。
foo x;
x.act(); // okay
const foo y;
y.act(); // UB!
これはSOの他の場所で説明されていますが、はメンバーもであり、オブジェクトの変更は未定義の動作であるconst foo
ことを意味します。counter
const
const
mutable
これが、 :の側で誤りを犯すべき理由ですconst_cast
。まったく同じ保証を与えるわけではありません。マークcounter
されていたら、それはオブジェクトmutable
ではなかったでしょう。const
さて、mutable
1つの場所で必要な場合は、どこでも必要です。必要がない場合は注意が必要です。確かに、これはすべてのスレッドセーフメンバーにマークmutable
を付ける必要があることを意味しますか?
いいえ、すべてのスレッドセーフメンバーが内部同期のために存在するわけではないためです。最も簡単な例は、ある種のラッパークラスです(必ずしもベストプラクティスではありませんが、存在します)。
struct threadsafe_container_wrapper
{
void missing_function_I_really_want()
{
container.do_this();
container.do_that();
}
const_container_view other_missing_function_I_really_want() const
{
return container.const_view();
}
threadsafe_container container;
};
ここではthreadsafe_container
、必要な別のメンバー関数をラップして提供しています(実際には無料の関数としてより良いでしょう)。ここでは必要ありませんmutable
。古い観点からの正確さは完全に勝っています。1つの関数ではコンテナを変更していますが、そうしないとは言わなかったので問題ありません(省略しconst
ます)。コンテナを変更し、その約束を守っていることを確認します(省略mutable
)。
Herbは、私たちが使用するほとんどの場合、mutable
ある種の内部(スレッドセーフ)同期オブジェクトも使用していると主張していると思います。私は同意します。エルゴの視点はほとんどの場合機能します。しかし、私がたまたまスレッドセーフなオブジェクトを持っていて、それをさらに別のメンバーとして扱う場合があります。この場合、の古くて基本的な使用法に頼りますconst
。