ポインタ変換が含まれるため、コードの一部に問題があります。これらのインスタンスには、関連するタイプが標準レイアウトであるためreinterpret_cast<T*>(e)
のセマンティクスがあることに注意してください。(実際、ストレージを扱うときは常にviaを使用するstatic_cast<T*>(static_cast<void*>(e))
ことをお勧めします。)static_cast
cv void*
標準をよく読むと、ポインタの変換中に実際に関係T*
するオブジェクトが実際に存在すると想定されていることがわかりますT*
。これは、型の些細なことのおかげで「不正行為」をしている場合でも、スニペットの一部で実行するのは困難です。関与します(これについては後で詳しく説明します)。しかし、それは要点を超えているでしょう...
エイリアシングはポインタ変換に関するものではありません。これは、3.10の左辺値と右辺値[basic.lval]から、一般に「厳密なエイリアシング」ルールと呼ばれるルールの概要を示すC++11テキストです。
10プログラムが、次のいずれかのタイプ以外のglvalueを介してオブジェクトの保存された値にアクセスしようとした場合、動作は未定義です。
- オブジェクトの動的タイプ、
- オブジェクトの動的タイプのcv修飾バージョン。
- オブジェクトの動的タイプに類似したタイプ(4.4で定義)、
- オブジェクトの動的型に対応する符号付きまたは符号なしの型である型、
- オブジェクトの動的型のcv修飾バージョンに対応する符号付きまたは符号なし型である型。
- 要素または非静的データメンバー(再帰的に、サブアグリゲートまたは含まれるユニオンの要素または非静的データメンバーを含む)の中に前述のタイプの1つを含む集合体または共用体タイプ。
- オブジェクトの動的型の(おそらくcv修飾された)基本クラス型である型、
- char型またはunsignedchar型。
(これは、C ++ 03の同じ節と副節の段落15ですが、テキストにいくつかの小さな変更があります。たとえば、後者はC ++ 11の概念であるため、「glvalue」の代わりに「lvalue」が使用されます。)
magic_cast<T*>(p)
これらの規則に照らして、実装が「どういうわけか」ポインタを別のポインタ型に変換するものを提供すると仮定しましょう。通常、これはでありreinterpret_cast
、場合によっては不特定の結果が得られますが、前に説明したように、これは標準レイアウトタイプへのポインタには当てはまりません。次に、すべてのスニペットが正しい(で置き換えreinterpret_cast
られるmagic_cast
)ことは明らかです。これは、の結果にglvalueがまったく含まれていないためですmagic_cast
。
これは間違って使用しているように見えるスニペットですがmagic_cast
、私が主張するのは正しいです:
// assume constexpr max
constexpr auto alignment = max(alignof(int), alignof(short));
alignas(alignment) char c[sizeof(int)];
// I'm assuming here that the OP really meant to use &c and not c
// this is, however, inconsequential
auto p = magic_cast<int*>(&c);
*p = 42;
*magic_cast<short*>(p) = 42;
私の推論を正当化するために、この表面的に異なるスニペットを想定します。
// alignment same as before
alignas(alignment) char c[sizeof(int)];
auto p = magic_cast<int*>(&c);
// end lifetime of c
c.~decltype(c)();
// reuse storage to construct new int object
new (&c) int;
*p = 42;
auto q = magic_cast<short*>(p);
// end lifetime of int object
p->~decltype(0)();
// reuse storage again
new (p) short;
*q = 42;
このスニペットは慎重に作成されています。特に、3.8オブジェクトの存続期間[basic.life]のパラグラフ5に規定されている規則のために破壊されたとしても、new (&c) int;
私は使用を許可されています。同じのパラグラフ6は、ストレージへの参照に非常によく似たルールを示し、パラグラフ7は、ストレージが再利用されるとオブジェクトを参照するために使用された変数、ポインター、および参照に何が起こるかを説明します-まとめて3.8/5と呼びます- 7。&c
c
この場合&c
、(暗黙的に)に変換されますvoid*
。これは、まだ再利用されていないストレージへのポインターの正しい使用法の1つです。同様に、新しいものが構築される前p
から取得されます。その定義は、実装の魔法の深さに応じて、破壊後に移動する可能性がありますが、構築後ではありません。段落7が適用され、これは許可された状況の1つではありません。オブジェクトの構築は、ストレージへのポインターになることにも依存しています。&c
int
c
int
short
p
int
とは些細なタイプなので、short
デストラクタへの明示的な呼び出しを使用する必要はありません。コンストラクターへの明示的な呼び出しも必要ありません(つまり、で宣言された通常の標準配置への呼び出し<new>
)。3.8からオブジェクトの存続期間[basic.life]:
1 [...]タイプTのオブジェクトの存続期間は、次の場合に始まります。
- タイプTの適切な配置とサイズのストレージが取得され、
- オブジェクトに自明でない初期化がある場合、その初期化は完了です。
タイプTのオブジェクトの存続期間は、次の場合に終了します。
- Tが自明でないデストラクタ(12.4)を持つクラス型の場合、デストラクタ呼び出しが開始されます。
- オブジェクトが占有するストレージは、再利用または解放されます。
q
これは、中間変数を折りたたんだ後、元のスニペットになるようにコードを書き直すことができることを意味します。
p
折りたたむことはできませんのでご注意ください。つまり、次のことは間違いなく正しくありません。
alignas(alignment) char c[sizeof(int)];
*magic_cast<int*>(&c) = 42;
*magic_cast<short*>(&c) = 42;
int
オブジェクトが(自明に)2行目で構築されていると仮定すると、それ&c
は再利用されたストレージへのポインターになることを意味する必要があります。したがって、3行目は正しくありません。ただし、3.8 / 5-7が原因であり、厳密に言えばエイリアシングルールが原因ではありません。
それを想定しない場合、2行目はエイリアシングルールの違反です。許可された例外の1つではない、char c[sizeof(int)]
タイプのglvalueを介して実際にオブジェクトであるものを読み取っています。int
比較すると、問題ありません(オブジェクトは3行目に自明に構築されている*magic_cast<unsigned char>(&c) = 42;
と想定します)。short
Alfと同様に、ストレージを使用する場合は、新しい標準配置を明示的に使用することをお勧めします。些細なタイプの破棄をスキップすることは問題ありませんが、遭遇*some_magic_pointer = foo;
すると、3.8 / 5-7の違反(そのポインターがどれほど魔法のように取得されたとしても)またはエイリアシングルールの違反に直面する可能性が非常に高くなります。これは、新しい式の結果も保存することを意味します。これは、オブジェクトが構築されると、マジックポインターを再利用できない可能性が高いためです。これは、3.8/5-7が原因です。
ただし、オブジェクトのバイトを読み取ること(つまり、char
またはunsigned char
を使用することは問題ありません)であり、reinterpret_cast
魔法を使用することすらありません。static_cast
viacv void*
は、間違いなくその仕事には問題ありません(ただし、Standardではより適切な表現を使用できると思います)。