サーチ…


構文

  • クラス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正しさに適用することもできますが、これはあまり頻繁に使用されません。

リフレクション:

ISO_CPP

constの正確さに私を売る

C ++チュートリアル

基礎

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修飾インスタンスの両方を取得できるようにし、メンバ関数が呼び出されたときにインスタンスのthisconst 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正しいコードがそうしないと仮定するのが一般的に安全です。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow