デストラクタの奇妙な列挙型


83

現在、のソースコードを読んでいますがProtocol Bufferここでenum定義されている奇妙なコードが1つ見つかりました。

  ~scoped_ptr() {
    enum { type_must_be_complete = sizeof(C) };
    delete ptr_;
  }

  void reset(C* p = NULL) {
    if (p != ptr_) {
      enum { type_must_be_complete = sizeof(C) };
      delete ptr_;
      ptr_ = p;
    }
  }

なぜenum { type_must_be_complete = sizeof(C) };ここで定義されているのですか?それは何のために使われますか?


2
確かにしたい場合ptr_sizeof、のsizeof(*ptr_)代わりにasで自分自身を使用したいと思いsizeof(C)ます。
ナワズ

回答:


81

このトリックは、このデストラクタのコンパイル時にCの定義が使用可能であることを保証することにより、UBを回避します。そうしないと、sizeof不完全な型(前方宣言型)を判別できないため、コンパイルは失敗しますが、ポインターは使用できます。

コンパイルされたバイナリでは、このコードは最適化され、効果はありません。

注:不完全な型の削除は、5.3.5 / 5:からの未定義の動作である可能性があります。

削除されるオブジェクトの削除時点でのクラスタイプが不完全であり、完全なクラスに重要なデストラクタまたは割り当て解除関数がある場合、動作は未定義です。

g++ 次の警告も発行します。

警告:削除演算子の呼び出しで問題の可能性が検出されました:
警告:「p」の型が不完全です
警告:「structC」の前方宣言


1
「不完全な型の削除は未定義の動作です」は正しくありません。タイプに重要なデストラクタがある場合は、UBのみです。この小さなトリックが対処する問題は、不完全な型の削除が常にUBであるとは限らないため、言語がそれをサポートすることです。
乾杯とhth。-アルフ

ありがとう@ Cheersandhth.-Alf私のポイントはそれがUBかもしれないということだったので、一般的にこのコード行はUBです。編集。
Mohit Jain

32

sizeof(C)が完全な型でない場合、コンパイル時に失敗しますC。ローカルスコープenumをそれに設定すると、実行時にステートメントが無害になります。

これは、プログラマーが自分自身から自分自身を保護する方法です。delete ptr_不完全な型での後続の動作は、重要なデストラクタがある場合は定義されていません。


1
その時点でCが完全な型である必要がある理由を説明できますdeleteか?それを呼び出すには完全な型定義が必要ですか?もしそうなら、なぜコンパイラはとにかくそれをキャッチしないのですか?
ピーターハル

1
それはむしろ避けることではありませんC = voidか?場合Cだけでは未定義のタイプだった、ではないでしょうdeleteステートメントはすでに失敗しますか?
Kerrek SB 2015

1
MohitJainが答えを持っているようです。
ピーターハル

1
-1「ローカルスコープの列挙型を設定すると、実行時にステートメントが無害になります。」ええと、無意味です。申し訳ありません。
乾杯とhth。-アルフ

1
@SteveJessopありがとう。私が見逃していたのは、不完全な型を削除するのはUBであるということでした。
ピーターハル

28

を理解するにはenum、それなしでデストラクタを検討することから始めます。

~scoped_ptr() {
    delete ptr_;
}

はどこptr_ですC*。タイプがいる場合C、この時点で不完全である、すなわち、すべてのコンパイラが知っているとされstruct C;、その後、(1)デフォルトで生成され、何も行わないデストラクタは、Cのインスタンスのために使用されていると指摘しました。これは、スマートポインタによって管理されるオブジェクトに対して行うべき正しいことではない可能性があります。

不完全な型へのポインタを介した削除で常に未定義の動作があった場合、標準ではコンパイラに診断を要求して失敗する可能性があります。しかし、実際のデストラクタが些細なものである場合、それは明確に定義されています。プログラマーは持つことができるが、コンパイラーは持つことができない知識です。言語がこれを定義して許可する理由は私にはわかりませんが、C ++は、今日ではベストプラクティスとは見なされていない多くのプラクティスをサポートしています。

完全な型のサイズは既知であるため、sizeof(C)C完全な型である場合にのみ、既知のデストラクタを使用してコンパイルされます。そのため、ガードとして使用できます。1つの方法は単純です

(void) sizeof(C);  // Type must be complete

いくつかのコンパイラーとオプションを使用すると、コンパイラーは、コンパイルすべきではないことに気付く前にそれを最適化し、そのような不適合なコンパイラーの動作を回避する方法であると推測しますenum

enum { type_must_be_complete = sizeof(C) };

enum単に破棄された表現ではなく、選択するための代替の説明は、単に個人的な好みです。

または、James T. Huggetがこの回答へのコメントで示唆しているように、「列挙型は、コンパイル時に疑似ポータブルエラーメッセージを作成する方法である可能性があります」。


(1)不完全な型に対してデフォルトで生成された何もしないデストラクタは、oldの問題でしたstd::auto_ptr。それは非常に陰湿だったので、国際的なC ++標準化委員会のハーブサッターの委員長によって書かれたPIMPLイディオムに関するGOTWアイテムになりました。もちろん、現在std::auto_ptrは非推奨ですが、代わりに他のメカニズムを使用します。


4
列挙型は、コンパイル時に疑似ポータブルエラーメッセージを作成する方法である可能性があります。
ブライスM.デンプシー

1
この答えはコードの動機を非常によく説明していると思いますが、(1)一部のコンパイラーはsizeof(T)コンパイルに失敗する代わりに不完全な型について0と評価していることを付け加えたいと思います。ただし、これは不適合な動作です。(2)C ++ 11以降、を使用static_assert((sizeof(T) > 0), "T must be a complete type");することは優れた(そして慣用的な)ソリューションになります。
5gon12eder 2015年

@ 5gon12eder; 「sizeof(T)不完全な型については0と評価される」C ++コンパイラの例を提供してください。
乾杯とhth。-アルフ

1
私はそのようなコンパイラを使ったことがないので、それらがもうなくなったと聞いても驚かないでしょう。そして、そうでない場合でも、不適合な実装を気にしないことは有効な決定です。それにもかかわらず、libstdc ++libc ++の両方static_assert(sizeof(T) > 0, "…");が、のそれぞれの実装でstd::unique_ptr、型が完全であることを確認するために使用します…
5gon12eder 2015年

1
…だから、これは慣用的だと言っても差し支えないと思います。とにかく、sizeof(T)ブール値のコンテキストで評価するsizeof(T) > 0ことはテストとまったく同じであるため、おそらく美的理由を除いて、それは実際には重要ではありません。
5gon12eder 2015年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.