constとconstexprの比較

この記事は偉大なる神のブログから移されたもので、ここで崇拝します。

const

C ++では、constは変数と関数を変更するために使用でき、目的ごとに異なる意味を持ちます。以下は説明です。

constセマンティクス

C ++でのconstの目的は、コンパイラーを介してオブジェクトの一定の性質を保証し、コンパイラーにconstオブジェクトの一定の性質に違反する可能性のあるすべての操作をエラーとして処理させることです。
オブジェクトの一定性は、物理的一定性(つまり、各ビットを変更できない)と論理的一定性(つまり、オブジェクトの動作が変更されない)の2つのタイプに分けることができます。物理定数は、次の例のようにC ++で使用されます。

struct A {
    
    
    int *ptr;
};
int k = 5, r = 6;
const A a = {
    
    &k};
a.ptr = &r;  // error
*a.ptr = 7;  // no error

aはconstオブジェクトであり、aのメンバーを割り当てるとエラーと見なされますが、ptrを変更せずに、ptrが指すオブジェクトを変更すると、コンパイラーはエラーを報告しません。Aのパフォーマンスが変更されたため、これは実際には論理定数に違反します。

論理定数のもう1つの機能は、constオブジェクト内にユーザーに表示されないドメインが存在する可能性があり、それらを変更しても論理定数に違反しないことです。効果的なC ++の例は次のとおりです。

class CTextBlock {
    
     
public: 
    ... 
    std::size_t length() const; 
private: 
    char *pText; 
    std::size_t textLength;            // last calculated length of textblock 
    bool lengthIsValid;                // whether length is currently valid 
};

CTextBlockオブジェクトがlengthメソッドを呼び出すたびに、現在の長さがtextLengthメンバーにキャッシュされ、lengthIsValidオブジェクトはキャッシュの有効性を表します。このシナリオでは、textLengthとlengthIsValidが変更されても、CTextBlockオブジェクトの論理定数に違反しませんが、オブジェクトの一部のビットが変更されているため、コンパイラによってブロックされます。C ++でこの問題を解決するために、mutableキーワードが追加されています。

このセクションの要約:C ++でのconstのセマンティクスは物理定数を保証することですが、論理定数の一部はmutableキーワードを介してサポートできます。

const変更された変数

前のセクションで述べたように、constを使用して変数を変更するセマンティクスでは、コンパイラーが変数へのすべての割り当てを防止する必要があります。したがって、const変数の初期値は、初期化時に指定する必要があります。

const int i;
i = 5;  // error
const int j = 10;  // ok

この初期値は、コンパイル時に決定される値、または実行時に決定される値にすることができます。整数型のconst変数にコンパイル時の初期値を指定すると、宣言時にこの変数を配列の長さとして使用できます。

const int COMPILE_CONST = 10;
const int RunTimeConst = cin.get();
int a1[COMPLIE_CONST];  // ok in C++ and error in C
int a2[RunTimeConst];  // error in C++

C ++コンパイラは、配列の長さのコンパイル時定数をそのリテラル値に直接置き換えることができるため、これは自動マクロ置換と同等です。(gcc検証では、配列の長さのみが直接置き換えられ、COMPILE_CONSTで割り当てられた他の場所は置き換えられなかったことがわかりました。)

ファイルドメインのconst変数は、デフォルトでファイルに表示されます。b.cppのa.cppでconst変数Mを使用する必要がある場合は、Mの初期化時にexternを追加する必要があります。

// a.cpp
extern const int M = 20;
 
// b.cpp
extern const int M;

変数の定義を.hファイルに入れると、.hファイルを含むすべての.cppファイルにこの変数の定義が含まれるようになり、リンク中に競合が発生すると一般に考えられています。ただし、const変数の定義を.hファイルに配置することは可能です。コンパイラーはこの変数を各.cppファイルの匿名名前空間に配置するため、異なる変数であり、リンクの競合は発生しません。(注:ただし、ヘッダーファイルのconst量の初期値が特定の関数に依存し、この関数の各呼び出しの戻り値が固定されていない場合、異なるコンパイル単位で見られるconst量の値が発生します不平等であると推測します。現時点では、const量を特定のクラスの静的メンバーとして使用すると、この問題を解決できる場合があります。)

constで変更されたポインターと参照

constが参照を変更する場合、その意味は変数を変更するのと同じです。しかし、constがポインターを変更する場合、ルールはもう少し複雑になります。

簡単に言えば、ポインタ変数のタイプは、変数名の左側に最も近い「*」に従って2つの部分に分けることができます。右側の部分はポインタ変数の性質を表し、左側の部分はそれが指す要素:

const int *p1;  // p1 is a non-const pointer and points to a const int
int * const p2;  // p2 is a const pointer and points to a non-const int
const int * const p3;  // p3 is a const pointer and points to a const int
const int *pa1[10];  // pa1 is an array and contains 10 non-const pointer point to a const int
int * const pa2[10];  // pa2 is an array and contains 10 const pointer point to a non-const int
const int (* p4)[10];  // p4 is a non-const pointer and points to an array contains 10 const int
const int (*pf)();  // pf is a non-const pointer and points to a function which has no arguments and returns a const int
...

constポインタの解釈規則はほとんど同じです...

ポインター自体はconstであり、これはポインターを割り当てることができないことを意味し、ポインターはconstであり、これはポインターを割り当てることができないことを意味します。したがって、参照はそれ自体がconstであるポインターと見なすことができ、const参照はconst Type * constポインターです。

constへのポインターは、non-constを指すポインターに割り当てることはできず、const参照を非const参照に割り当てることはできませんが、その逆は問題ではありません。これは、constセマンティクスが破壊されないようにするためでもあります。

const_castを使用してポインターまたは参照のconstプロパティを削除するか、static_castを使用してconstプロパティを非constポインターまたは参照に追加できます。

int i;
const int *cp = &i;
int *p = const_cast<int *>(cp);
const int *cp2 = static_cast<const int *>(p);  // here the static_cast is optional

C ++クラスのthisポインターは、それ自体がconstであるポインターであり、クラスのconstメソッドのthisポインターは、それ自体とそのポイントが両方ともconstであるポインターです。

クラスのconstメンバー変数

クラスのconstメンバー変数は、非静的定数と静的定数の2つのタイプに分けることができます。

非静的定数:

クラスの非静的メンバーはコンストラクターの関数本体に入る前に構築され、const定数は構築中に初期化される必要があるため、クラスの非静的定数はコンストラクターの初期化リストで初期化する必要があります。割り当てはコンパイラによってブロックされます。

class B {
    
    
public:
    B(): name("aaa") {
    
    
        name = "bbb";  // error
    }
private:
    const std::string name;
};

静的定数:

静的定数はクラス内で直接宣言されますが、クラス外の定義と初期値のみが必要です。一般的な方法は、クラスの静的定数の定義を対応する.cppに含めることです。

// a.h
class A {
    
    
    ...
    static const std::string name;
};
 
// a.cpp
const std::string A::name("aaa");

特殊なケースは、静的定数の型がchar、int、size_tなどの組み込み整数型である場合、初期値をクラスで直接指定できるため、定義する必要がない場合です。クラスの外で。コンパイラーは、この静的定数を対応する初期値に直接置き換えます。これは、マクロの置き換えと同等です。ただし、マクロのように値を使用するのではなく、アドレスを取得するなど、コードで通常の変数のようにこの静的定数を使用する場合でも、クラスの外部で定義を提供する必要がありますが、初期値は必要ありません(それはすでにステートメントに存在します)。

// a.h
class A {
    
    
    ...
    static const int SIZE = 50; 
};
 
// a.cpp
const int A::SIZE = 50;  // if use SIZE as a variable, not a macro

const変更された関数

C ++では、constを使用してクラスの非静的メンバー関数を変更できます。そのセマンティクスは、関数に対応するオブジェクトの定数を保証することです。constメンバー関数では、他のメンバー変数への代入、非constメソッドの呼び出し、オブジェクトの呼び出しなど、このポインターのconstnessに違反する可能性のあるすべての操作(constメンバー関数のこのポインターはdouble constポインターです)がブロックされます。 non-constメソッド自体。ただし、可変として宣言されたメンバー変数に対して実行された操作はブロックされません。ここでは、特定の論理定数が保証されています。

さらに、constが関数を変更すると、関数のオーバーロードにも関与します。つまり、constオブジェクト、constポインター、または参照を介してメソッドを呼び出す場合、constメソッドが最初に呼び出されます。

class A {
    
    
public:
    int &operator[](int i) {
    
    
        ++cachedReadCount;
        return data[i];
    }
    const int &operator[](int i) const {
    
    
        ++size; // !error
        --size; // !error
        ++cachedReadCount; // ok
        return data[i];
    }
private:
    int size;
    mutable cachedReadCount;
    std::vector<int> data;
};
 
A &a = ...;
const A &ca = ...;
int i = a[0]; // call operator[]
int j = ca[0]; // call const operator[]
a[0] = 2; // ok
ca[0] = 2; // !error

この例では、2つのバージョンのoperator []のコードが基本的に同じである場合、コードの再利用を実現するために、関数の1つで別の関数を呼び出すことを検討してください(Effective C ++を参照)。ここでは、constバージョンを呼び出すために非constバージョンのみを使用できます。

int &A::operator[](int i) {
    
    
    return const_cast<int &>(static_cast<const A &>(*this).operator[](i));
}

その中で、自分自身を呼び出して無限ループが発生するのを避けるために、最初に* thisをconstA&に変換します。これはstatic_castを使用して実行できます。const operator []の戻り値を取得した後、そのconstを手動で削除する必要があります。これは、const_castを使用して実行できます。一般的に、const_castは推奨されませんが、ここでは、処理しているオブジェクトが実際には非constであることが明確にわかっているため、ここでconst_castを使用しても安全です。

constexpr

constexprはC ++ 11の新しいキーワードであり、そのセマンティクスは「定数式」、つまりコンパイル時に評価できる式です。最も基本的な定数式は、リテラル値、グローバル変数/関数アドレス、sizeofなどのキーワードによって返される結果ですが、その他の定数式は、さまざまな決定論的操作による基本式によって取得されます。constexpr値は、列挙型、スイッチ、配列の長さなどに使用できます。

constexprによって変更された変数は、コンパイル時に評価可能である必要があり、変更された関数は、そのすべてのパラメーターがconstexprである場合にconstexprを返す必要があります。

constexpr int Inc(int i) {
    
    
    return i + 1;
}
 
constexpr int a = Inc(1); // ok
constexpr int b = Inc(cin.get()); // !error
constexpr int c = a * 2 + 1; // ok

constexprを使用して、クラスのコンストラクターを変更することもできます。つまり、コンストラクターに提供されるパラメーターがconstexprの場合、生成されたオブジェクトのすべてのメンバーがconstexprになります。これは、constexprオブジェクトでもあります。 constexprを使用できるさまざまなタイプの場所に使用されます。constexprコンストラクターには空の関数本体が必要であることに注意してください。つまり、すべてのメンバー変数の初期化は初期化リストに配置されます。

struct A {
    
    
    constexpr A(int xx, int yy): x(xx), y(yy) {
    
    }
    int x, y;
};
 
constexpr A a(1, 2);
enum {
    
    SIZE_X = a.x, SIZE_Y = a.y};

constexprの利点:

  1. プログラムの正しいセマンティクスが破壊されないようにすることは、非常に強い制約です。
  2. トランスレータは、コンパイル時にconstexprコードに対して非常に大規模な最適化を実行できます。たとえば、使用されるすべてのconstexpr式を最終結果に直接置き換えることができます。
  3. マクロと比較して、追加のオーバーヘッドはありませんが、より安全で信頼性があります。

おすすめ

転載: blog.csdn.net/qq_24649627/article/details/110631381