初心者のC++プログラマとして、私にはまだ非常に不明瞭に見える構造がいくつかあります。これらの1つはconst
です。あなたは非常に多くの場所でそれを使用することができ、初心者が生きて出てくることはほとんど不可能である非常に多くの異なる効果で。一部のC++の第一人者は、さまざまな用途と、それらを使用しないかどうか、および/または使用しない理由を永遠に説明しますか?
いくつかの用途を収集しようとしています:
一部の一時的な参照を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; }
};
copy-on-writeクラスにconstを使用。コピーする必要があるときとそうでないときをコンパイラーが判断できるようにします。
struct MyString {
char * getData() { /* copy: caller might write */ return mData; }
char const* getData() const { return mData; }
};
説明:元のデータとコピーされたオブジェクトのデータが同じままである限り、何かをコピーするときにデータを共有することができます。ただし、オブジェクトの1つがデータを変更すると、2つのバージョンが必要になります。1つはオリジナル用で、もう1つはコピー用です。つまり、いずれかのオブジェクトにwriteでcopyするため、現在、両方に独自のバージョンがあります。
コードの使用:
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++ライブラリがコピーオンライトstd::string
を実装しているため、GCCに同じアドレスを出力します。両方の文字列は、別個のオブジェクトであるにもかかわらず、文字列データ用に同じメモリを共有します。 b
を非constにすると、operator[]
のnon-constバージョンが優先され、GCCはバッキングメモリバッファーのコピーを作成します。変更できるため、a
!
int main() {
string const a = "1234";
string b = a;
// outputs different addresses!
cout << (void*)&a[0] << ", " << (void*)&b[0];
}
コピーコンストラクターがconstオブジェクトおよび一時からコピーを作成するため:
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としてマークしました。しかし、C++は、学生でGetName()メソッドを呼び出してもオブジェクトが変更されないことをどのように知っていましたか?答えは、メソッドがconstとしてマークされたということです。
class Student {
public:
string GetName() const { ... }
};
メソッドに「const」をマークすると、2つのことが行われます。主に、このメソッドがオブジェクトを変更しないことをC++に伝えます。 2番目のことは、すべてのメンバー変数がconstとしてマークされているかのように扱われることです。これは役立ちますが、クラスのインスタンスの変更を妨げるものではありません。
これは非常にシンプルな例ですが、質問への回答に役立つことを願っています。
これら4つの宣言の違いを理解するように注意してください。
次の2つの宣言は意味的に同じです。 whereccp1と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
ちょっとした注意として、私が here を読んでいると、
const
は、そのすぐ左にあるものに適用されます(そこに何もない場合を除き、その場合は、そのすぐ右にあるものに適用されます)。