C++
Const Correctness
サーチ…
構文
- クラスClassOne {public:bool non_modifying_member_function()const {/ * ... * /}};
- int ClassTwo :: non_modifying_member_function()const {/ * ... * /}
- void ClassTwo :: modification_member_function(){/ * ... * /}
- char non_param_modding_func(const ClassOne&one、const ClassTwo * two){/ * ... * /}
- float parameter_modifying_function(ClassTwo&one、ClassOne * two){/ * ... * /}
- 短いClassThree :: non_modding_non_param_modding_f(const ClassOne&)const {/ * ... * /}
備考
const
correctnessは、プログラマーが誤ってコードを変更している可能性がある関数をすばやく特定できるため、非常に便利なトラブルシューティングツールです。また、 Const Correct Function Parameters
示されているような意図しないエラーが正しくコンパイルされずに気付かれないようにします。
のためのクラスを設計する方がはるかに簡単ですconst
それは後から追加するよりも、正確const
既存のクラスに正確さを。可能な場合は、することができ 、任意のクラス設計const
それがあるように正しいconst
自分と他人に後でそれを変更する手間を節約するために、正しいを。
これは、必要に応じてconst
精度と同じ規則でvolatile
正しさに適用することもできますが、これはあまり頻繁に使用されません。
リフレクション:
基礎
const
正しさは、インスタンスを変更する必要があるコードだけがインスタンスを変更できる (つまり書き込みアクセスが可能)、逆にインスタンスを変更する必要のないコードは実行できないようにコードを設計するプラクティスですそう(すなわち、読み取りアクセスのみを有する)。これにより、インスタンスが意図せずに変更されるのを防ぎ、コードのエラーを減らし、コードがインスタンスの状態を変更するかどうかを記録します。また、インスタンスを変更する必要がないときはいつでもconst
として扱い、初期化後に変更する必要がなければconst
として定義して、機能を失うことなくインスタンスを処理することができます。
これは、メンバー関数にconst
CV修飾子を与え 、ポインタ/参照パラメータをconst
にすることによって行われます(ただし、書き込みアクセスが必要な場合を除く)。
class ConstCorrectClass {
int x;
public:
int getX() const { return x; } // Function is const: Doesn't modify instance.
void setX(int i) { x = i; } // Not const: Modifies instance.
};
// Parameter is const: Doesn't modify parameter.
int const_correct_reader(const ConstCorrectClass& c) {
return c.getX();
}
// Parameter isn't const: Modifies parameter.
void const_correct_writer(ConstCorrectClass& c) {
c.setX(42);
}
const ConstCorrectClass invariant; // Instance is const: Can't be modified.
ConstCorrectClass variant; // Instance isn't const: Can be modified.
// ...
const_correct_reader(invariant); // Good. Calling non-modifying function on const instance.
const_correct_reader(variant); // Good. Calling non-modifying function on modifiable instance.
const_correct_writer(variant); // Good. Calling modifying function on modifiable instance.
const_correct_writer(invariant); // Error. Calling modifying function on const instance.
constの正確性の性質から、これはクラスのメンバ関数から始まり、外側に向かって動作します。あなたが非呼び出そうとconst
からメンバ関数をconst
インスタンス、または非からconst
として扱われているインスタンスconst
、コンパイラはあなたにそれがCV-修飾子を失うことについてのエラーが発生します。
Const正しいクラスデザイン
const
-correctクラスでは、論理状態を変更しないすべてのメンバ関数は、 this
cv修飾されたconst
持ち、オブジェクトを変更しないことを示します( const
インスタンスでも自由に変更できる任意のmutable
フィールド); const
cv修飾された関数が参照を返す場合、その参照もconst
なければなりません。 const T*
はT*
またはconst T*
いずれかにバインドすることができるので、定数と非CV修飾インスタンスの両方で呼び出すことができます。これにより、関数は、機能を失うことなく、渡された参照パラメータを変更する必要がないときにconst
として宣言することができます。
さらに、 const
正しいクラスでは、 Const Correct Function Parameters
で説明したように、参照渡しされたすべての関数パラメータはconst
正しいものになるため、関数が明示的に修正する必要がある場合にのみ変更できます。
まず、 this
cv-qualifierを見てみましょう:
// Assume class Field, with member function "void insert_value(int);".
class ConstIncorrect {
Field fld;
public:
ConstIncorrect(Field& f); // Modifies.
Field& getField(); // Might modify. Also exposes member as non-const reference,
// allowing indirect modification.
void setField(Field& f); // Modifies.
void doSomething(int i); // Might modify.
void doNothing(); // Might modify.
};
ConstIncorrect::ConstIncorrect(Field& f) : fld(f) {} // Modifies.
Field& ConstIncorrect::getField() { return fld; } // Doesn't modify.
void ConstIncorrect::setField(Field& f) { fld = f; } // Modifies.
void ConstIncorrect::doSomething(int i) { // Modifies.
fld.insert_value(i);
}
void ConstIncorrect::doNothing() {} // Doesn't modify.
class ConstCorrectCVQ {
Field fld;
public:
ConstCorrectCVQ(Field& f); // Modifies.
const Field& getField() const; // Doesn't modify. Exposes member as const reference,
// preventing indirect modification.
void setField(Field& f); // Modifies.
void doSomething(int i); // Modifies.
void doNothing() const; // Doesn't modify.
};
ConstCorrectCVQ::ConstCorrectCVQ(Field& f) : fld(f) {}
Field& ConstCorrectCVQ::getField() const { return fld; }
void ConstCorrectCVQ::setField(Field& f) { fld = f; }
void ConstCorrectCVQ::doSomething(int i) {
fld.insert_value(i);
}
void ConstCorrectCVQ::doNothing() const {}
// This won't work.
// No member functions can be called on const ConstIncorrect instances.
void const_correct_func(const ConstIncorrect& c) {
Field f = c.getField();
c.do_nothing();
}
// But this will.
// getField() and doNothing() can be called on const ConstCorrectCVQ instances.
void const_correct_func(const ConstCorrectCVQ& c) {
Field f = c.getField();
c.do_nothing();
}
次にこれをConst Correct Function Parameters
と組み合わせると、クラスが完全にconst
correctになります。
class ConstCorrect {
Field fld;
public:
ConstCorrect(const Field& f); // Modifies instance. Doesn't modify parameter.
const Field& getField() const; // Doesn't modify. Exposes member as const reference,
// preventing indirect modification.
void setField(const Field& f); // Modifies instance. Doesn't modify parameter.
void doSomething(int i); // Modifies. Doesn't modify parameter (passed by value).
void doNothing() const; // Doesn't modify.
};
ConstCorrect::ConstCorrect(const Field& f) : fld(f) {}
Field& ConstCorrect::getField() const { return fld; }
void ConstCorrect::setField(const Field& f) { fld = f; }
void ConstCorrect::doSomething(int i) {
fld.insert_value(i);
}
void ConstCorrect::doNothing() const {}
これは、に基づいてオーバーロードと組み合わせることができますconst
インスタンスがある場合、我々は1つの行動をしたい場合には、ネスconst
、および異なる振る舞いそうでない場合は、コンテナ自体が非const
場合にのみ変更を許可するアクセサを提供することを一般的に使用します。
class ConstCorrectContainer {
int arr[5];
public:
// Subscript operator provides read access if instance is const, or read/write access
// otherwise.
int& operator[](size_t index) { return arr[index]; }
const int& operator[](size_t index) const { return arr[index]; }
// ...
};
これは一般的に、ほとんどのコンテナを取るためにオーバーロードを提供するとともに、標準ライブラリで使用されているconst
口座にネスを。
Const修正関数のパラメータ
const
-correct関数では、関数が直接的または間接的にそれらを変更しない限り、渡されたすべての参照パラメータはconst
としてマークされ、プログラマが誤って変更したくないものを変更するのを防ぎます。これは、関数がconst
インスタンスと非cv修飾インスタンスの両方を取得できるようにし、メンバ関数が呼び出されたときにインスタンスのthis
をconst T*
型にします。ここで、 T
はクラスの型です。
struct Example {
void func() { std::cout << 3 << std::endl; }
void func() const { std::cout << 5 << std::endl; }
};
void const_incorrect_function(Example& one, Example* two) {
one.func();
two->func();
}
void const_correct_function(const Example& one, const Example* two) {
one.func();
two->func();
}
int main() {
Example a, b;
const_incorrect_function(a, &b);
const_correct_function(a, &b);
}
// Output:
3
3
5
5
これの効果は少なく、すぐに目に見えるのものよりますがconst
正しいクラス設計(その中const
-correct関数やconst
-incorrectクラスコンパイルエラーが発生します、一方、 const
-correctクラスとconst
-incorrectの機能が正常にコンパイルされます)、 const
正しいです関数は、エラーがたくさんキャッチしますconst
下記のような、すり抜け聞かせ不正確な機能を。 [ただし、 const
-incorrect関数は、コンパイル・エラーが発生します渡された場合const
それは非期待するとき、インスタンスをconst
ものを。]
// Read value from vector, then compute & return a value.
// Caches return values for speed.
template<typename T>
const T& bad_func(std::vector<T>& v, Helper<T>& h) {
// Cache values, for future use.
// Once a return value has been calculated, it's cached & its index is registered.
static std::vector<T> vals = {};
int v_ind = h.get_index(); // Current working index for v.
int vals_ind = h.get_cache_index(v_ind); // Will be -1 if cache index isn't registered.
if (vals.size() && (vals_ind != -1) && (vals_ind < vals.size()) && !(h.needs_recalc())) {
return vals[h.get_cache_index(v_ind)];
}
T temp = v[v_ind];
temp -= h.poll_device();
temp *= h.obtain_random();
temp += h.do_tedious_calculation(temp, v[h.get_last_handled_index()]);
// We're feeling tired all of a sudden, and this happens.
if (vals_ind != -1) {
vals[vals_ind] = temp;
} else {
v.push_back(temp); // Oops. Should've been accessing vals.
vals_ind = vals.size() - 1;
h.register_index(v_ind, vals_ind);
}
return vals[vals_ind];
}
// Const correct version. Is identical to above version, so most of it shall be skipped.
template<typename T>
const T& good_func(const std::vector<T>& v, Helper<T>& h) {
// ...
// We're feeling tired all of a sudden, and this happens.
if (vals_ind != -1) {
vals[vals_ind] = temp;
} else {
v.push_back(temp); // Error: discards qualifiers.
vals_ind = vals.size() - 1;
h.register_index(v_ind, vals_ind);
}
return vals[vals_ind];
}
ドキュメントとしてのConst Correctness
const
正確さについてのより有用なことの1つは、コードを文書化する方法として機能し、プログラマおよび他のユーザにある程度の保証を提供することである。これらの保証は、コンパイラによってconst
ために強制されますが、コードがconst
持たないことを示します。
const
CV限定メンバー関数:
-
const
メンバ関数は、インスタンスを読み込もうとしていると想定できます。- 呼び出されたインスタンスの論理的な状態は変更しないでください。したがって、それらは、変更
mutable
変数を除いて、呼び出されたインスタンスのメンバ変数を変更してはならない。 -
mutable
変数を除いて、インスタンスのメンバ変数を変更する他の関数を呼び出してはいけません。
- 呼び出されたインスタンスの論理的な状態は変更しないでください。したがって、それらは、変更
- 逆に、
const
でないメンバ関数は、インスタンスを変更しようとしていると想定できます。- 論理状態を変更してもしなくてもよい。
- 論理状態を変更する他の関数を呼び出しても呼び出しなくてもよい。
これは、関数の定義を見ていなくても、与えられたメンバ関数が呼び出された後に、オブジェクトの状態についての仮定を立てるために使うことができます:
// ConstMemberFunctions.h
class ConstMemberFunctions {
int val;
mutable int cache;
mutable bool state_changed;
public:
// Constructor clearly changes logical state. No assumptions necessary.
ConstMemberFunctions(int v = 0);
// We can assume this function doesn't change logical state, and doesn't call
// set_val(). It may or may not call squared_calc() or bad_func().
int calc() const;
// We can assume this function doesn't change logical state, and doesn't call
// set_val(). It may or may not call calc() or bad_func().
int squared_calc() const;
// We can assume this function doesn't change logical state, and doesn't call
// set_val(). It may or may not call calc() or squared_calc().
void bad_func() const;
// We can assume this function changes logical state, and may or may not call
// calc(), squared_calc(), or bad_func().
void set_val(int v);
};
const
規則のため、これらの前提は実際コンパイラによって強制されます。
// ConstMemberFunctions.cpp
ConstMemberFunctions::ConstMemberFunctions(int v /* = 0*/)
: cache(0), val(v), state_changed(true) {}
// Our assumption was correct.
int ConstMemberFunctions::calc() const {
if (state_changed) {
cache = 3 * val;
state_changed = false;
}
return cache;
}
// Our assumption was correct.
int ConstMemberFunctions::squared_calc() const {
return calc() * calc();
}
// Our assumption was incorrect.
// Function fails to compile, due to `this` losing qualifiers.
void ConstMemberFunctions::bad_func() const {
set_val(863);
}
// Our assumption was correct.
void ConstMemberFunctions::set_val(int v) {
if (v != val) {
val = v;
state_changed = true;
}
}
const
関数のパラメータ:
-
const
である1つ以上のパラメータを持つ関数は、それらのパラメータを読み込もうとしていると想定できます。- これらのパラメータを変更したり、それらを変更するメンバ関数を呼び出したりしないでください。
- それらのパラメータを変更したり、それらを変更するメンバ関数を呼び出す他の関数にこれらのパラメータを渡してはいけません。
- 逆に、
const
ではない1つ以上のパラメータを持つ関数は、これらのパラメータを変更する意図があると想定できます。- これらのパラメータを変更したり、変更したりすることはできません。また、それらを変更できるメンバー関数を呼び出すこともできます。
- それらのパラメータを変更したり、それらを変更するメンバ関数を呼び出す他の関数にこれらのパラメータを渡してもしなくてもよい。
これは、関数の定義を見ていなくても、与えられた関数に渡された後のパラメータの状態を前提にするために使用できます。
// function_parameter.h
// We can assume that c isn't modified (and c.set_val() isn't called), and isn't passed
// to non_qualified_function_parameter(). If passed to one_const_one_not(), it is the first
// parameter.
void const_function_parameter(const ConstMemberFunctions& c);
// We can assume that c is modified and/or c.set_val() is called, and may or may not be passed
// to any of these functions. If passed to one_const_one_not, it may be either parameter.
void non_qualified_function_parameter(ConstMemberFunctions& c);
// We can assume that:
// l is not modified, and l.set_val() won't be called.
// l may or may not be passed to const_function_parameter().
// r is modified, and/or r.set_val() may be called.
// r may or may not be passed to either of the preceding functions.
void one_const_one_not(const ConstMemberFunctions& l, ConstMemberFunctions& r);
// We can assume that c isn't modified (and c.set_val() isn't called), and isn't passed
// to non_qualified_function_parameter(). If passed to one_const_one_not(), it is the first
// parameter.
void bad_parameter(const ConstMemberFunctions& c);
const
規則のため、これらの前提は実際コンパイラによって強制されます。
// function_parameter.cpp
// Our assumption was correct.
void const_function_parameter(const ConstMemberFunctions& c) {
std::cout << "With the current value, the output is: " << c.calc() << '\n'
<< "If squared, it's: " << c.squared_calc()
<< std::endl;
}
// Our assumption was correct.
void non_qualified_function_parameter(ConstMemberFunctions& c) {
c.set_val(42);
std::cout << "For the value 42, the output is: " << c.calc() << '\n'
<< "If squared, it's: " << c.squared_calc()
<< std::endl;
}
// Our assumption was correct, in the ugliest possible way.
// Note that const correctness doesn't prevent encapsulation from intentionally being broken,
// it merely prevents code from having write access when it doesn't need it.
void one_const_one_not(const ConstMemberFunctions& l, ConstMemberFunctions& r) {
// Let's just punch access modifiers and common sense in the face here.
struct Machiavelli {
int val;
int unimportant;
bool state_changed;
};
reinterpret_cast<Machiavelli&>(r).val = l.calc();
reinterpret_cast<Machiavelli&>(r).state_changed = true;
const_function_parameter(l);
const_function_parameter(r);
}
// Our assumption was incorrect.
// Function fails to compile, due to `this` losing qualifiers in c.set_val().
void bad_parameter(const ConstMemberFunctions& c) {
c.set_val(18);
}
const
正確さを回避することは可能ですが 、これらの保証を破ることは可能ですが 、これはプログラマーが意図的に行う必要があります(上記のMachiavelli
でカプセル化を破るのと同じです)。
class DealBreaker : public ConstMemberFunctions {
public:
DealBreaker(int v = 0);
// A foreboding name, but it's const...
void no_guarantees() const;
}
DealBreaker::DealBreaker(int v /* = 0 */) : ConstMemberFunctions(v) {}
// Our assumption was incorrect.
// const_cast removes const-ness, making the compiler think we know what we're doing.
void DealBreaker::no_guarantees() const {
const_cast<DealBreaker*>(this)->set_val(823);
}
// ...
const DealBreaker d(50);
d.no_guarantees(); // Undefined behaviour: d really IS const, it may or may not be modified.
しかし、これにより、プログラマは 、コンパイラに対して、 const
を無視し、コンパイラ間で矛盾していることをコンパイラに明確に伝える必要があるため、特に指定しない限り、 const
正しいコードがそうしないと仮定するのが一般的に安全です。