ライブラリ内の関数が実際に何かを変更するかどうかを判断するのはどれくらい簡単ですか?
簡単にわかり、そうでない場合はconst_cast
、const ポインター/非 const への参照を使用して、ライブラリ関数を呼び出すことができます。これを行うために、ライブラリ クラスの周りにラッパーをスローすることをお勧めします。これは面倒で冗長ですが、そのコードをクラスから取得します。このラッパーは、ライブラリ クラスを使用する方法で動作できるかどうかに応じて、いくつかの const アクセサーを追加するサブクラスになる可能性があります。
判断が難しい場合、または実際に変更が加えられている場合は、コード内で非 const インスタンスとライブラリ クラスへの参照を使用する必要があります。mutable
タイプ(2)のものには役立ちますが、タイプ(1)のものについては、非定数引数を渡すだけで済みます。
それが難しい理由の例として、ライブラリの作成者が次のようなものを書いたとします。
struct Foo {
size_t times_accessed;
int value;
int get() {
++times_accessed;
return value;
}
};
const_cast
のconst
インスタンスでFoo
を呼び出すと、未定義の動作が発生しますget()
[*]。したがって、それが呼び出されたオブジェクトを本当に変更しないことを確認する必要があります。非 const インスタンスへの const 参照を取得しても、get
の const インスタンスを作成しないようにすることで、これを少し軽減できます。Foo
そうすれば、あなたとあなたconst_cast
に電話get
するとき、少なくともUBを引き起こさない. 関数が変更しないと主張するオブジェクトのフィールドが変更され続けるため、コードが混乱する可能性があります。
[*] なぜ未定義の動作なのですか? const
オブジェクトの値が有効なプログラムで決して変更されないことを言語が保証できるようにするためには、そうする必要があります。この保証により、コンパイラは有用なことを行うことができます。たとえば、static const
オブジェクトを読み取り専用のデータ セクションに配置したり、既知の値を使用してコードを最適化したりできます。const
また、可視の初期化子を持つ整数オブジェクトがコンパイル時の定数であることも意味します。これは、配列のサイズまたはテンプレート引数として使用できるようにするために標準で使用されます。const オブジェクトを変更するのが UB でない場合、const オブジェクトは定数ではなく、次のことは不可能です。
#include <iostream>
struct Foo {
int a;
Foo(int a) : a(a) {}
};
void nobody_knows_what_this_does1(const int *p); // defined in another TU
void nobody_knows_what_this_does2(const int *p); // defined in another TU
int main() {
const Foo f(1);
Foo g(1);
nobody_knows_what_this_does1(&f.a);
nobody_knows_what_this_does2(&g.a);
int x;
if (std::cin >> x) {
std::cout << (x / f.a); // Optimization opportunity!
std::cout << (x / g.a); // Cannot optimize!
}
}
f
は const オブジェクトであり、したがっても const オブジェクトであるため、関数の最後で使用されるf.a
と、オプティマイザーf.a
は の値が 1 であることを認識します。選択した場合、除算を最適化することができます。:は const オブジェクトではなく、それへのポインターが不明なコードに渡されたため、その値が変更された可能性があります。したがって、あなたがorの作成者であり、それを使用して参照先を変更することを考えている場合、参照先が非定数であることを何らかの方法で知っている場合にのみ、それを行うことができます。通常は使用しないため、通常は使用しません。g.a
g
nobody_knows_what_this_does1
nobody_knows_what_this_does2
const_cast
p
const_cast