初心者の C++ プログラマーの私には、まだ非常にわかりにくい構造がいくつかあります。そのうちの 1 つがconst
. 非常に多くの場所で使用でき、非常に多くの異なる効果があり、初心者が生き残ることはほとんど不可能です. 一部の C++ の第一人者は、さまざまな用途と、それらを使用するかどうか、および/または使用しない理由を永遠に説明しますか?
4 に答える
いくつかの使用法を収集しようとしています:
一時的なものを const への参照にバインドして、寿命を延ばします。参照はベースにすることができます-そしてそのデストラクタは仮想である必要はありません-正しいデストラクタが引き続き呼び出されます:
ScopeGuard const& guard = MakeGuard(&cleanUpFunction);
説明、コードを使用:
struct ScopeGuard {
~ScopeGuard() { } // not virtual
};
template<typename T> struct Derived : ScopeGuard {
T t;
Derived(T t):t(t) { }
~Derived() {
t(); // call function
}
};
template<typename T> Derived<T> MakeGuard(T t) { return Derived<T>(t); }
このトリックは、Alexandrescu の ScopeGuard ユーティリティ クラスで使用されます。一時がスコープ外になると、Derived のデストラクタが正しく呼び出されます。上記のコードには細部がいくつか欠けていますが、それが大きな問題です。
const を使用して、他のメソッドがこのオブジェクトの論理状態を変更しないことを伝えます。
struct SmartPtr {
int getCopies() const { return mCopiesMade; }
};
コピー オン ライト クラスに const を使用して、いつコピーする必要があるか、いつコピーする必要がないかをコンパイラが判断できるようにします。
struct MyString {
char * getData() { /* copy: caller might write */ return mData; }
char const* getData() const { return mData; }
};
説明: 元のオブジェクトとコピーされたオブジェクトのデータが同じままである限り、何かをコピーするときにデータを共有したい場合があります。ただし、オブジェクトの 1 つがデータを変更すると、2 つのバージョンが必要になります。1 つはオリジナル用で、もう 1 つはコピー用です。つまり、いずれかのオブジェクトへの書き込み時にコピーするため、両方が独自のバージョンを持つようになります。
コードの使用:
int main() {
string const a = "1234";
string const b = a;
// outputs the same address for COW strings
cout << (void*)&a[0] << ", " << (void*)&b[0];
}
上記のスニペットは、使用されている C++ ライブラリが copy-on-write を実装しているため、GCC に同じアドレスを出力しますstd::string
。どちらの文字列も別個のオブジェクトですが、文字列データ用に同じメモリを共有します。non -const を作成するとb
、 の non-const バージョンが優先され、operator[]
GCC はバッキング メモリ バッファーのコピーを作成しますa
。
int main() {
string const a = "1234";
string b = a;
// outputs different addresses!
cout << (void*)&a[0] << ", " << (void*)&b[0];
}
copy-constructor が const オブジェクトと temporaries からコピーを作成するには:
struct MyClass {
MyClass(MyClass const& that) { /* make copy of that */ }
};
自明に変更できない定数を作成するため
double const PI = 3.1415;
値ではなく参照によって任意のオブジェクトを渡すため - 高価または不可能な値による受け渡しを防ぐため
void PrintIt(Object const& obj) {
// ...
}
C++ の const には、主に 2 つの用途があります。
定数値
値が、有効期間中に変更されない (または変更されるべきではない) 変数、メンバー、またはパラメーターの形式である場合は、const とマークする必要があります。これにより、オブジェクトのミューテーションを防ぐことができます。たとえば、次の関数では、渡された Student インスタンスを変更する必要がないため、const とマークします。
void PrintStudent(const Student& student) {
cout << student.GetName();
}
なぜあなたがこれをするのかについて。基になるデータが変更できないことがわかっている場合、アルゴリズムについて推論するのははるかに簡単です。「const」は役立ちますが、これが達成されることを保証するものではありません。
明らかに、データを cout に出力することはあまり考えなくても構いません :)
メンバー メソッドを const としてマークする
前の例では、Student を const としてマークしました。しかし、student で GetName() メソッドを呼び出してもオブジェクトが変更されないことを、C++ はどのようにして知ったのでしょうか? 答えは、メソッドが const としてマークされていることです。
class Student {
public:
string GetName() const { ... }
};
メソッドを「const」とマークすると、2 つのことが行われます。主に、このメソッドがオブジェクトを変更しないことを C++ に伝えます。2 つ目は、すべてのメンバー変数が const としてマークされているかのように扱われるようになったことです。これは役に立ちますが、クラスのインスタンスの変更を妨げるものではありません。
これは非常に単純な例ですが、問題の解決に役立つことを願っています。
これら 4 つの宣言の違いを理解するように注意してください。
次の 2 つの宣言は意味的に同じです。ccp1 と ccp2 が指す場所は変更できますが、指している対象を変更することはできません。
const char* ccp1;
char const* ccp2;
次に、ポインターは const であるため、意味を持たせるには、何かを指すように初期化する必要があります。他のものを指すようにすることはできませんが、指すものは変更できます。
char* const cpc = &something_possibly_not_const;
最後に、この 2 つを結合します。つまり、指している対象は変更できず、ポインターは他の場所を指すことができません。
const char* const ccpc = &const_obj;
時計回りのスパイラル ルールは、宣言のもつれを解くのに役立ちます http://c-faq.com/decl/spiral.anderson.html
ちょっとしたメモとして、私がここで読んだように、それに気付くと便利です
const
すぐ左にあるものに適用されます (ただし、何もない場合はすぐ右にあるものに適用されます)。