スタンダードC++
- Const correctness
- “const correctness”とは何ですか?
- “const correctness”は通常の型安全性とどのように関連していますか?
- const正しいものを”すぐに”または”後で”取得しようとする必要がありますか?
- “const X*p”とはどういう意味ですか?
- “const X*p”、”X*const p”、”const X*const p”の違いは何ですか?
- “const X&x”とはどういう意味ですか?
- “X&const x”は意味がありますか?
- “constメンバー関数”とは何ですか?
- 参照ごとの戻り値とconstメンバー関数の関係は何ですか?
- “const-overloading”の取引は何ですか?
- 論理状態と物理状態を区別すると、より良いクラスを設計するのにどのように役立ちますか?
- 私のパブリックメンバー関数の定数は、メソッドがオブジェクトの論理状態または物理状態に対して何をするかに基づいている必要がありますか?
- constメンバー関数でデータメンバーに”見えない”変更を加えたい場合はどうすればよいですか?
- const_castは最適化の機会が失われたことを意味しますか?
- なぜコンパイラはconst int*でそれを指した後にintを変更することを許可しますか?
- “const Fred*p”は*pが変更できないことを意味しますか?
Const correctness
“const correctness”とは何ですか?
いいことだ。 これは、キーワードconst
を使用して、const
オブジェクトが変更されないようにすることを意味します。
たとえば、std::string
を受け入れる関数f()
を作成し、f()
に渡される呼び出し元のstd::string
を変更することをcallersnotに約束したい場合は、f()
にstd::string
パラメータを受け取ることがで…
-
void f1(const std::string& s);
// 参照渡し-へ-const
-
void f2(const std::string* sptr);
// ポインタで渡す-に-const
-
void f3(std::string s);
// 値渡し
参照渡しconst
およびポインタ渡しconst
の場合、f()
関数内で呼び出し元のstd::string
を変更しようとすると、コンパイラによってエラーとしてフラグが付けられ コンパイル時。 このチェックはコンパイル時に完全に実行されます:const
の実行時スペースや速度コストはありません。 値渡しの場合(f3()
)、呼び出された関数は呼び出し元のstd::string
のコピーを取得します。 これは、f3()
がlocalcopyを変更できることを意味しますが、f3()
が戻るとコピーは破棄されます。 特にf3()
は呼び出し元のstd::string
オブジェクトを変更できません。逆の例として、std::string
を受け入れる関数g()
を作成したいが、g()
が呼び出し元のstd::string
オブジェクトを変更する可能性があることをcallersに知らせたいとします。 この場合、g()
にstd::string
パラメータを受け取ることができます…
-
void g1(std::string& s);
// 参照から非へのパス-const
-
void g2(std::string* sptr);
// ポインタから非へのパス-const
これらの関数にconst
がないことは、コンパイラに、コンパイラのstd::string
オブジェクトを変更することが許可されている(ただし必須ではない)ことを伝えます。 したがって、彼らはstd::string
をf()
関数のいずれかに渡すことができますが、f3()
(そのパラメータを”値で”受け取るもの)だけがstd::string
をg1()
またはg2()
に渡すことができます。 f1()
またはf2()
がg()
関数のいずれかを呼び出す必要がある場合、std::string
オブジェクトのローカルコピーをg()
関数に渡す必要があります。f1()
またはf2()
へのパラメータをg()
関数に直接渡すことはできません。 E.g.,
void g1(std::string& s);void f1(const std::string& s){ g1(s); // Compile-time Error since s is const std::string localCopy = s; g1(localCopy); // Okay since localCopy is not const}
当然のことながら、上記の場合、g1()
が行う変更は、f1()
にローカルなlocalCopy
オブジェクトに行われます。f1()
を参照して渡されたconst
パラメータには変更は行われません。f1()
を参照して渡されたconst
パラメータには変更は行われません。
“const correctness”は通常の型安全性とどのように関連していますか?
パラメータのconst
-nessを宣言することは、型安全性の単なる別の形式です。
通常の型安全性がシステムを正しくするのに役立つことがわかった場合(特に大規模なシステムでは)、const
正しさも役立ちます。
const
正しさの利点は、意図していなかった何かを誤って変更することを防ぐことです変更される可能性があります。 コンパイラや他のプログラマに重要な意味情報のいくつかの追加の部分を伝えているthebenefitで、いくつかの余分なキーストローク(const
キーワード)でコードを飾る必要
概念的には、const std::string
は通常のstd::string
とは異なるクラスであると想像することができます。const
バリアントは概念的には非const
バリアントで利用可能なさまざまな変 たとえば、概念的には、const std::string
には代入演算子+=
やその他の突然変異演算がないと想像できます。
const正しいものを”すぐに”または”後で”取得しようとする必要がありますか?
非常に、非常に、非常に初めに。
バックパッチconst
正しさは雪だるま効果をもたらします:”ここに”を追加するすべてのconst
は、”あそこに”を追加する必要があります。”
const
を早く頻繁に追加します。
“const X*p”とはどういう意味ですか?
p
はX
クラスのオブジェクトを指していることを意味しますが、p
はX
オブジェクトを変更するために使用できません(当然p
はNULL
も使用できます)。
右から左に読む:”pは定数であるXへのポインタです。たとえば、クラスX
にinspect() const
のようなconst
メンバー関数がある場合、p->inspect()
と言っても大丈夫です。 しかし、クラスX
にmutate()
という非const
メンバ関数がある場合、p->mutate()
と言うとエラーになります。
重要なことに、このエラーはコンパイル時にコンパイラによってキャッチされます-ランタイムテストは行われません。 つまり、const
はあなたのプログラムを遅くせず、実行時に物事をチェックするために余分なテストケースを書く必要はありません-thecompilerはコンパイル時に作業
“const X*p”、”X*const p”、”const X*const p”の違いは何ですか?
ポインタ宣言を右から左に読み込みます。
-
const X* p
“p
はX
を指しているconst
“を意味します。X
オブジェクトはp
を介して変更することはできません。 -
X* const p
は、”p
がconst
以外のX
へのポインタである”ことを意味します。const
: ポインタp
自体を変更することはできませんが、p
を介してX
オブジェクトを変更することはできます。 -
const X* const p
は、”p
はconst
であるX
へのconst
ポインタである”ことを意味します。p
自体を変更することも、p
を介してX
オブジェクトを変更することもできません。p
を介してX
オブジェクトを変更することもできません。
そして、ああ、いや、私はあなたのポインタ宣言を右から左に読むことに言及しましたか?
“const X&x”とはどういう意味ですか?
x
はX
オブジェクトのエイリアスを意味しますが、x
を介してそのX
オブジェクトを変更することはできません。
右から左に読む: “x
はX
、つまりconst
への参照です。たとえば、クラスX
にinspect() const
のようなconst
メンバー関数がある場合、x.inspect()
と言っても大丈夫です。 しかし、クラスX
にmutate()
という非const
メンバー関数がある場合、x.mutate()
と言うとエラーになります。
これは、コンパイラがコンパイル時にすべてのチェックを行うという事実を含め、constへのポインタと完全に対称的です。const
はプログラムを遅くせず、実行時に物事をチェックするために余分なテストケースを書く必要はありません。”X const&x”と”X const*p”はどういう意味ですか?
X const& x
はconst X& x
に相当し、X const* x
はconst X* x
に相当します。
一部の人々は、”一貫性のあるconst
“と呼んだり、Simon Brandによって造語された用語を使用して、”Eastconst
“と呼んだりして、正しいスタイルを好む人もいます。”確かに、”Eastconst
“スタイルは代替よりも一貫性があります:”Eastconst
“スタイルは常にそれが構成するものの右側にconst
を置きますが、他のスタイルは時々const
を左に置き、時には右に置くことがあります(const
ポインタ宣言とconst
メンバ関数の場合)。
“Eastconst
“スタイルでは、const
であるローカル変数が右のconst
で定義されます:int const a = 42;
。 同様に、const
であるstatic
変数はstatic double const x = 3.14;
として定義されます。基本的にすべてのconst
は、右にある必要があるconst
を含む、それが構築するものの右側に終わります:const
ポインタ宣言とconst
メンバー関数。
“Eastconst
“スタイルも型エイリアスと一緒に使用すると混乱が少なくなります。foo
とbar
はなぜここで異なる型を持っていますか?
using X_ptr = X*;const X_ptr foo;const X* bar;
“Eastconst
“スタイルを使用すると、これがより明確になります:
using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;
ここでは、foo
とfoobar
が同じ型であり、bar
が異なる型であることがより明確です。
“Eastconst
“スタイルもポインタ宣言とより一貫しています。 伝統的なスタイルを対比:
const X** foo;const X* const* bar;const X* const* const baz;
“Eastconst
“スタイル
X const** foo;X const* const* bar;X const* const* const baz;
これらの利点にもかかわらず、const
-on-the-rightスタイルはまだ普及していないため、レガシーコードは伝統的なスタイルを持つ傾向があります。
“X&const x”は意味がありますか?
いいえ、それはナンセンスです。
上記の宣言が何を意味するのかを知るには、右から左に読んでください: “x
はconst
への参照ですX
“。 しかし、それは冗長です—参照は常にconst
、あなたはそれが別のオブジェクトを参照させるためにareferenceを再シートすることはできませんという意味で。 決して。 const
の有無にかかわらず。つまり、”X& const x
“は”X& x
“と機能的に同等です。 それは人々を混乱させるでしょう—const
は、あなたが”const X& x
“と言ったかのように、X
がconst
であると思う人もいます。
“constメンバー関数”とは何ですか?
そのオブジェクトを検査する(変更するのではなく)メンバー関数。
const
メンバー関数は、メンバー関数のパラメーターリストの直後にconst
サフィックスで示されます。 サフィックスconst
を持つMemberfunctionsは、”const
メンバー関数”または”インスペクタ”と呼ばれます。”const
サフィックスのないメンバー関数は、”非const
メンバー関数”または”ミューテータ”と呼ばれます。”
class Fred {public: void inspect() const; // This member promises NOT to change *this void mutate(); // This member function might change *this};void userCode(Fred& changeable, const Fred& unchangeable){ changeable.inspect(); // Okay: doesn't change a changeable object changeable.mutate(); // Okay: changes a changeable object unchangeable.inspect(); // Okay: doesn't change an unchangeable object unchangeable.mutate(); // ERROR: attempt to change unchangeable object}
unchangeable.mutate()
を呼び出そうとすると、コンパイル時にエラーが発生します。 const
には実行時スペースやspeedpenaltyはなく、実行時にテストケースを記述してチェックする必要はありません。
inspect()
メンバー関数の末尾のconst
は、メソッドがオブジェクトのabstract(client-visible)状態を変更しないことを意味するために使用する必要があります。 これは、メソッドがtheobjectのstruct
の”生のビット”を変更しないということとは少し異なります。 C++コンパイラは、通常は解決できないaliasing問題を解決できない限り、”ビットごとの”解釈を取ることはできません(つまり、オブジェクトの状態を変更する このエイリアシングの問題からの別の(重要な)洞察: pointer-to-const
を持つオブジェクトを指すことは、オブジェクトが変更されないことを保証するものではありません。
参照ごとの戻り値とconstメンバー関数の関係は何ですか?
インスペクタメソッドからの参照によってthis
オブジェクトのメンバーを返す場合は、constへの参照(const X& inspect() const
)または値(X inspect() const
)を使用して返す必要があります。
class Person {public: const std::string& name_good() const; // Right: the caller can't change the Person's name std::string& name_evil() const; // Wrong: the caller can change the Person's name int age() const; // Also right: the caller can't change the Person's age // ...};void myCode(const Person& p) // myCode() promises not to change the Person object...{ p.name_evil() = "Igor"; // But myCode() changed it anyway!!}
良いニュースは、あなたがこれを間違って取得した場合、コンパイラが頻繁にあなたをキャッチするということです。 特に、上記のPerson::name_evil()
のように、this
オブジェクトのメンバーをconst
以外の参照で誤って返すと、コンパイラはそれを検出し、この場合はPerson::name_evil()
の内部をコンパイル中にコンパイ
悪いニュースは、コンパイラが常にあなたを捕まえるとは限らないということです。
翻訳:あなたは考える必要があります。 それはあなたを怖がらせる場合は、仕事の別の行を見つけます。
このセクション全体に広がる”const
哲学”を覚えておいてください:const
メンバー関数は、this
オブジェクトの論理状態(別名abstract状態別名meaningwisestate)を変更しない(または呼び出し元 オブジェクトが内部的にどのように実装されているのかではなく、オブジェクトの意味を考えてみてください。 人の年齢と名前は論理的にその人の一部ですが、その人の隣人と雇用主はそうではありません。 this
オブジェクトの論理的/抽象的/意味的状態の一部を返すインスペクタメソッドは、その部分がthis
オブジェクト内に物理的に埋め込まれた直接データメン
“const-overloading”の取引は何ですか?
const
オーバーロードはconst
の正しさを達成するのに役立ちます。
const
オーバーロードは、インスペクタメソッドとミューテータメソッドが同じ名前で、同じ数のパラメータとタイプのパラメータを持つ場合です。 二つの異なるメソッドは、inspectorがconst
であり、mutatorが非const
であることだけが異なります。
const
オーバーロードの最も一般的な使用法は、添字演算子を使用することです。 通常、std::vector
などの標準コンテナテンプレートのいずれかを使用する必要がありますが、subscriptoperatorを持つ独自のクラスを作成する必要がある場合は、経験則があります。
class Fred { /*...*/ };class MyFredList {public: const Fred& operator (unsigned index) const; // Subscript operators often come in pairs Fred& operator (unsigned index); // Subscript operators often come in pairs // ...};
const
添字演算子はconst
参照を返すため、コンパイラは呼び出し元がFred
を誤って変更/変更するのを防ぎます。 非const
添字演算子は、呼び出し元(およびコンパイラ)がFred
オブジェクトを変更することを許可されていることを伝える方法である、非const
参照を返します。
MyFredList
クラスのユーザーが添字演算子を呼び出すと、コンパイラはMyFredList
の定数に基づいて呼び出すオーバーロードを選択します。 呼び出し元がMyFredList a
またはMyFredList& a
を持っている場合、a
はconst
以外の添字演算子を呼び出し、呼び出し元はconst
以外の添字演算子への参照になります。Fred
:
たとえば、class Fred
にinspector-methodinspect() const
とmutator-methodmutate()
があるとします:
void f(MyFredList& a) // The MyFredList is non-const{ // Okay to call methods that inspect (look but not mutate/change) the Fred at a: Fred x = a; // Doesn't change to the Fred at a: merely makes a copy of that Fred a.inspect(); // Doesn't change to the Fred at a: inspect() const is an inspector-method // Okay to call methods that DO change the Fred at a: Fred y; a = y; // Changes the Fred at a a.mutate(); // Changes the Fred at a: mutate() is a mutator-method}
ただし、呼び出し元にconst MyFredList a
またはconst MyFredList& a
がある場合、a
はconst
subscriptoperatorを呼び出し、呼び出し元はFred
へのconst
参照になります。 これにより、発信者はa
でFred
を検査することができますが、発信者がa
でFred
を誤って変更/変更することを防ぎます。
void f(const MyFredList& a) // The MyFredList is const{ // Okay to call methods that DON'T change the Fred at a: Fred x = a; a.inspect(); // Compile-time error (fortunately!) if you try to mutate/change the Fred at a: Fred y; a = y; // Fortunately(!) the compiler catches this error at compile-time a.mutate(); // Fortunately(!) the compiler catches this error at compile-time}
添字演算子およびfuncall演算子のConstオーバーロードは、here、here、here、here、およびhereです。
もちろん、添字演算子以外のものにはconst
-overloadingを使用することもできます。
論理状態と物理状態を区別すると、より良いクラスを設計するのにどのように役立ちますか?
これは、クラスを内側からではなく外側から設計することを奨励するため、クラスとオブジェクトを理解して使用しやすく、直感的で、エラーが少なく、 (さて、それはわずかな過度の単純化です。 すべてのifとbutを理解するには、thisanswerの残りの部分を読むだけです!)
これを内側から理解しましょう-あなたは外側からクラスを設計する必要がありますが、この概念に慣れていない場合は、内側から理解する方が簡
内部では、あなたのオブジェクトは物理的な(または具体的な、またはビット単位の)状態を持っています。 クラスが単なるCスタイルのstruct
であれば、そこにある状態です。外部では、オブジェクトにクラスのユーザーがあり、これらのユーザーはpublic
memberfunctionsとfriend
のみを使用するように制限されています。 たとえば、オブジェクトがクラスRectangle
でメソッドwidth()
、height()
、およびarea()
の場合、ユーザーはそれらの3つがオブジェクトの論理的(または抽象的または意味的)状態のすべ 外部ユーザーにとって、Rectangle
objectは、その領域がその場で計算されている場合でも(たとえば、area()
メソッドがobjectの幅と高さの積を返す場合)、実際には領域を持ちます。 実際には、これは重要なポイントです、あなたのユーザーはこれらのメソッドのいずれかをどのように実装するかを知らず、気にしません; あなたのユーザーは、あなたのオブジェクトが論理的に幅、高さ、および面積のameaningwise状態を持っていることを、彼らの観点から認識しています。
area()
の例は、論理状態に物理状態で直接実現されていない要素を含めることができる場合を示しています。 クラスは、オブジェクトの物理的な(具体的な、ビット単位の)状態の一部をユーザーから意図的に隠すことがあります—ユーザーがこの隠された状態を読み書きしたり、知ることを可能にするpublic
メンバー関数やfriend
を意図的に提供しません。 つまり、オブジェクトの物理状態には、オブジェクトの論理状態に対応する要素がないビットがあることを意味します。
この後者のケースの例として、コレクションオブジェクトは、次のルックアップのパフォーマンスを向上させるために、最後のルックアップをキャッ このキャッシュは確かにオブジェクトの物理的な状態の一部ですが、そこにはおそらくユーザーに公開されないinternalimplementationの詳細があります。 あなたが外から考えるならば、簡単なことは何かを伝える-で: コレクションオブジェクトのユーザーがキャッシュ自体の状態を確認するためにnowayを持っている場合、キャッシュは透過的であり、オブジェクトのlogicalstate
私のパブリックメンバー関数の定数は、メソッドがオブジェクトの論理状態または物理状態に対して何をするかに基づいている必要がありますか?
この次の部分を簡単にする方法はありません。 それは傷つくだろう。 最もよい推薦は坐ることである。 そして、あなたの安全のために、近くに鋭い道具がないことを確認してください。
コレクションオブジェクトの例に戻りましょう。 覚えておいてください: 将来のルックアップを高速化するために、最後のルックアップをキャッシュするルックアップ方法があります。
おそらく明白なことを述べてみましょう:lookupメソッドがcollection-objectの論理状態のいずれにも変更を加えないと仮定します。
だから…あなたを傷つける時が来た。 準備はいいか?
ここに来る:lookupメソッドがcollection-objectの論理状態を変更しないが、collection-objectの物理状態を変更する場合(実際のキャッシュに非常に実際の変更を行います)、lookupメソッ
答えは”はい”です。 (すべてのルールには例外があるので、「はい」には実際にアスタリスクが付いているはずですが、大部分の場合、答えは「はい」です。)
これは、”物理的なconst
よりも”論理的なconst
“についてのすべてです。”それは、const
でamethodを飾るかどうかについての決定は、その方法が論理状態を変更しないかどうかに主に左右されるべきであることを意味します(あなたは座っていますか? メソッドがオブジェクトの非常に現実的な物理状態に非常に現実的な変化を起こすかどうかにかかわらず、(座っていることをお勧めします)。
沈んでいなかった場合、またはまだ痛みがない場合は、二つのケースに分けていじくりましょう:
- メソッドがオブジェクトの論理状態の一部を変更した場合、それは論理的にミューテータです。
const
evenifであってはなりません(実際に起こるように!)このメソッドは、オブジェクトの具体的な状態の物理的なビットを変更しません。 - 逆に、メソッドは論理的にはインスペクタであり、オブジェクトの論理状態の一部を決して変更しない場合は、(実際に起こったとしても)
const
でなければなりません。)このメソッドは、オブジェクトの具体的な状態の物理ビットを変更します。
混乱している場合は、もう一度読んでください。
あなたが混乱していないが怒っているなら、良い:あなたはまだそれを好きではないかもしれませんが、少なくともあなたはそれを理解しています。 深呼吸をして、私の後に繰り返してください:”メソッドのconst
ネスは、オブジェクトの外側から理にかなっているはずです。”メソッドの定数はオブジェクトのユーザーにとって意味をなさなければならず、それらのユーザーはオブジェクトの論理状態のみを見ることができます。”
まだ怒っているなら、申し訳ありませんが、それはそれです。 それを吸うし、それと一緒に暮らす。 はい、例外があります; すべてのルールはそれらを持っています。 しかし、原則として、主に、この論理的なconst
概念はあなたにとって良いものであり、あなたのソフトウェアにとって良いものです。
もう一つ。 これは無意味になりますが、メソッドがオブジェクトのlogicalstateを変更するかどうかについて正確に説明しましょう。 あなたがクラスの外にいる場合—あなたは通常のユーザーですが、実行できるすべての実験(呼び出すメソッドのすべてのメソッドorsequence)は、そのlookupメソッドを Lookup関数がfutureメソッドの将来の動作を変更した場合(単に高速化するだけでなく、結果を変更し、戻り値を変更し、theexceptionを変更した場合)、lookupメソッドはオブジェク しかし、lookupメソッドが、おそらくいくつかのことをより速くする以外に何も変更しなかった場合、それはインスペクタです。
constメンバー関数でデータメンバーに”見えない”変更を加えたい場合はどうすればよいですか?
はmutable
を使用します(または、最後の手段としてconst_cast
を使用します)。
わずかな割合の検査官が、externalusersでは観察できないオブジェクトの物理状態に変更を加える必要があります。
たとえば、前に説明したコレクションオブジェクトは、次の検索のパフォーマンスを向上させるために、最後の検索をキャッシュしました。 この例では、キャッシュはcollection-objectのpublic interface(タイミング以外)のどの部分でも直接監視できないため、その存在と状態はobjectのlogical状態の一部ではないため、外部ユー Lookupメソッドは、少なくとも現在の実装では、オブジェクトの物理状態を変更するという事実に関係なく、オブジェクトの論理状態を決して変更しな
メソッドが物理的ではあるが論理的ではない状態を変更する場合、メソッドは通常、インスペクタメソッドであるため、const
とマークする必要があります。 コンパイラがconst
メソッドがthis
オブジェクトの物理状態を変更しているのを見ると、文句を言います—コードにエラーメッセージが表示されます。
C++コンパイラ言語は、この論理的なconst
概念を受け入れるのに役立つmutable
キーワードを使用します。 この場合、mutable
キーワードでキャッシュをマークすると、コンパイラはconst
メソッド内または他のconst
ポインタまたは参照を介して変更することが許可されてい 私たちの専門用語では、mutable
キーワードは、論理状態の一部ではないオブジェクトの物理状態の部分をマークします。
mutable
キーワードは、データメンバーの宣言の直前、つまりconst
を置くことができる場所と同じ場所にあります。 もう1つのアプローチは、おそらくconst_cast
キーワードを介して、this
ポインタのconst
‘nessをキャストすることではありません:
Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!
この行の後、self
はthis
と同じビット、つまりself == this
を持ちますが、self
はconst Set*
ではなくSet*
です(技術的にはthis
はconst Set* const
ですが、右端のconst
はこの議論とは無関係です)。つまり、self
を使用してthis
が指すオブジェクトを変更することができます。
注:const_cast
で発生する可能性のある非常に低いエラーがあります。 それは三つの非常にまれなものが同時に組み合わされたときにのみ起こります: mutable
であるべきデータメンバー(上記のような)、mutable
キーワードをサポートしていないcompilerthat、および/またはそれを使用しないプログラマ、およびconst
であるとoriginallydefinedされたオブジェクこの組み合わせは非常にまれであり、決して起こらないかもしれませんが、それが起こった場合、コードが機能しない可能性があります(標準では動作
const_cast
を使用したい場合は、代わりにmutable
を使用してください。 つまり、anobjectのメンバーを変更する必要があり、そのオブジェクトがconst
へのポインタで指されている場合、最も安全で簡単なことはメンバーの宣言にmutable
を追 実際のオブジェクトがconst
ではないことが確実な場合はconst_cast
を使用できます(たとえば、オブジェクトがSet
s;
のように宣言されていることが確実な場合)が、オブジ
マシンZのコンパイラYのバージョンXがconst
オブジェクトの非mutable
メンバーを変更できると書いてはいけません。 私は気にしません—それは言語に応じて違法であり、あなたのコードはおそらく異なるコンパイラや同じコンパイラの別のバージョン(アップグレード)で ノーと言ってくれ 代わりにmutable
を使用してください。 コードを書くそれは動作することが保証されており、壊れていないように見えるコードではありません。
const_castは最適化の機会が失われたことを意味しますか?
理論的には、はい、実際には、いいえ。
言語がconst_cast
を非合法化したとしても、const
memberfunction呼び出し全体でレジスタキャッシュをフラッシュしないようにする唯一の方法は、エイリアシングの問題を解、オブジェクトを指す非const
ポインタがないことを証明するため)。 これはまれなケースでのみ発生する可能性があります(オブジェクトがconst
memberfunction呼び出しのスコープで構築され、オブジェクトの構築とconst
メンバ関数呼び出しの間のすべての非const
メンバ関数呼び出しが静的にバインドされ、これらの呼び出しのすべてがinline
dであり、コンストラクタ自体がinline
dであり、メンバ関数がコンストラクタ呼び出しがinline
である場合)。
なぜコンパイラはconst int*でそれを指した後にintを変更することを許可しますか?
“const int* p
“は”p
が*p
を変更しないことを約束することを意味するので、”not”*p
は変更しないことを約束します。”
const int*
がint
を指すようにすると、const
はint
ではありません。 int
はconst int*
を介して変更することはできませんが、他の誰かが同じint
(”エイリアス”)を指すint*
(注:const
なし)を持っている場合、そのint*
を使用してint
を変更できます。 例えば:
void f(const int* p1, int* p2){ int i = *p1; // Get the (original) value of *p1 *p2 = 7; // If p1 == p2, this will also change *p1 int j = *p1; // Get the (possibly new) value of *p1 if (i != j) { std::cout << "*p1 changed, but it didn't change via pointer p1!\n"; assert(p1 == p2); // This is the only way *p1 could be different }}int main(){ int x = 5; f(&x, &x); // This is perfectly legal (and even moral!) // ...}
main()
とf(const int*,int*)
は、週の異なる日にコンパイルされる異なるコンパイル単位にある可能性があることに注意してください。 その場合、コンパイラがコンパイル時にエイリアシングを検出できる方法はありません。 したがって、私たちはこの種のことを禁止する言語ルールを作ることができる方法はありません。 実際には、一般的には、同じものを指す多くのポインタを持つことができる機能と考えられているので、そのようなaruleを作りたくないでしょう。 これらのポインタの1つが、基礎となる「もの」を変更しないことを約束しているという事実は、ポインタによって行われた約束に過ぎません; それは”事”によって作られた約束ではありません。
“const Fred*p”は*pが変更できないことを意味しますか?
いや! (これはint
ポインタのエイリアシングに関するFAQに関連しています。)
“const Fred* p
” つまり、Fred
はポインタp
を介して変更することはできませんが、const
を経由せずにオブジェクトを取得する他の方法があるかもしれません(Fred*
などのエイ たとえば、同じFred
オブジェクト(エイリアシング)を指す2つのポインタ”const Fred* p
“と”Fred* q
“がある場合、pointerq
を使用してFred
オブジェクトを変更できますが、pointerp
は変更できません。Foo**→const Foo**の変換中にエラーが発生するのはなぜですか?
Foo**
→const Foo**
の変換は無効で危険です。
C++は(安全な)変換を許可しますFoo*
→Foo const*
ですが、暗黙的に変換しようとするとエラーが発生しますFoo**
→const Foo**
。
そのエラーがなぜ良いのかの根拠を以下に示します。 しかし、最初に、ここで最も一般的な解決策です:単純にconst Foo**
をconst Foo* const*
に変更します:
class Foo { /* ... */ };void f(const Foo** p);void g(const Foo* const* p);int main(){ Foo** p = /*...*/; // ... f(p); // ERROR: it's illegal and immoral to convert Foo** to const Foo** g(p); // Okay: it's legal and moral to convert Foo** to const Foo* const* // ...}
Foo**
→const Foo**
からの変換が危険である理由は、キャストなしでconst Foo
オブジェクトを静かに誤って変更できるからです:
class Foo {public: void modify(); // make some modification to the this object};int main(){ const Foo x; Foo* p; const Foo** q = &p; // q now points to p; this is (fortunately!) an error *q = &x; // p now points to x p->modify(); // Ouch: modifies a const Foo!! // ...}
もしq = &p
行が合法であれば、q
はp
を指しているでしょう。 次の行*q = &x
はp
自体を*q
がp
であるためx
を指すように変更します。 それはconst
修飾子を失ってしまったので、悪いことです:p
はFoo*
ですが、x
はconst Foo
です。 p->modify()
行はp
のreferentを変更する機能を利用しますが、これは実際の問題です。const Foo
を変更することになったためです。
類推すると、あなたが合法的な変装の下で犯罪者を隠す場合、彼はその変装に与えられた信頼を悪用することができます。それは悪いです。
ありがたいことに、C++ではこれを行うことができません。q = &p
行はc++コンパイラによってcompile-timeerrorとしてフラグが付けられています。 注意:コンパイル時のエラーメッセージの周りにポインタをキャストしないでください。 ちょうどノーと言う!
(注:これとDerived**
からBase**
への変換の禁止との間には概念的な類似性があります。)