281

ヘッダーに次のようなコードがあります。

#include <memory>

class Thing;

class MyClass
{
    std::unique_ptr< Thing > my_thing;
};

型定義を含まない cpp にこのヘッダーを含めると、ThingVS2010-SP1 でコンパイルされません。

1>C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\memory(2067): エラー C2027: 未定義の型 'Thing' の使用

に置き換えstd::unique_ptrstd::shared_ptrコンパイルします。

したがって、完全な定義が必要なのは現在の VS2010std::unique_ptrの実装であり、完全に実装に依存していると思います。

またはそれは?std::unique_ptrの実装が前方宣言のみで動作することを不可能にする標準要件に何かありますか? へのポインターだけを保持する必要があるため、奇妙に感じThingますね。

4

9 に答える 9

374

ここから採用。

C++ 標準ライブラリのほとんどのテンプレートでは、完全な型でインスタンス化する必要があります。ただしshared_ptr、 とunique_ptr部分的な例外です。すべてではありませんが、一部のメンバーは不完全な型でインスタンス化できます。これの動機は、未定義の動作を危険にさらすことなく、スマート ポインターを使用してpimplなどのイディオムをサポートすることです。

不完全な型があり、それを呼び出すと、未定義の動作が発生する可能性がありますdelete

class A;
A* a = ...;
delete a;

上記は法定コードです。コンパイルされます。コンパイラは、上記のような上記のコードに対して警告を発する場合としない場合があります。実行すると、おそらく悪いことが起こります。運が良ければ、プログラムがクラッシュします。~A()ただし、より可能性の高い結果は、プログラムが呼び出されないため、黙ってメモリをリークすることです。

上記の例で使用auto_ptr<A>しても役に立ちません。生のポインターを使用した場合と同じ未定義の動作が得られます。

それにもかかわらず、特定の場所で不完全なクラスを使用することは非常に便利です! これはどこにshared_ptrあり、unique_ptr助けます。これらのスマート ポインターのいずれかを使用すると、完全な型が必要な場合を除き、不完全な型を回避できます。そして最も重要なことは、完全な型が必要な場合に、その時点で不完全な型でスマート ポインターを使用しようとすると、コンパイル時にエラーが発生することです。

未定義の動作はもうありません:

コードがコンパイルされる場合は、必要なすべての場所で完全な型を使用しています。

class A
{
    class impl;
    std::unique_ptr<impl> ptr_;  // ok!

public:
    A();
    ~A();
    // ...
};

shared_ptrさまざまな場所でunique_ptr完全なタイプが必要です。理由は不明で、動的デリータと静的デリータに関係しています。正確な理由は重要ではありません。実際、ほとんどのコードでは、完全な型が必要な場所を正確に知ることはあまり重要ではありません。コーディングするだけで、間違っている場合はコンパイラーが教えてくれます。

ただし、参考になる場合は、完全性要件のいくつかのメンバーshared_ptrを文書化した表を次に示します。unique_ptrメンバーが完全なタイプを必要とする場合、エントリには「C」があり、それ以外の場合、テーブル エントリは「I」で埋められます。

Complete type requirements for unique_ptr and shared_ptr

                            unique_ptr       shared_ptr
+------------------------+---------------+---------------+
|          P()           |      I        |      I        |
|  default constructor   |               |               |
+------------------------+---------------+---------------+
|      P(const P&)       |     N/A       |      I        |
|    copy constructor    |               |               |
+------------------------+---------------+---------------+
|         P(P&&)         |      I        |      I        |
|    move constructor    |               |               |
+------------------------+---------------+---------------+
|         ~P()           |      C        |      I        |
|       destructor       |               |               |
+------------------------+---------------+---------------+
|         P(A*)          |      I        |      C        |
+------------------------+---------------+---------------+
|  operator=(const P&)   |     N/A       |      I        |
|    copy assignment     |               |               |
+------------------------+---------------+---------------+
|    operator=(P&&)      |      C        |      I        |
|    move assignment     |               |               |
+------------------------+---------------+---------------+
|        reset()         |      C        |      I        |
+------------------------+---------------+---------------+
|       reset(A*)        |      C        |      C        |
+------------------------+---------------+---------------+

ポインター変換を必要とする操作には、unique_ptrとの両方の完全な型が必要ですshared_ptr

コンストラクターは、コンパイラーが への呼び出しをセットアップする必要がない場合にのみunique_ptr<A>{A*}、不完全なものを回避できます。たとえば、 をヒープに置くと、不完全な を回避できます。この点の詳細については、BarryTheHatchet の回答(こちら) を参照してください。A~unique_ptr<A>()unique_ptrA

于 2011-05-22T15:54:34.967 に答える
50

コンパイラは、MyClass の既定のデストラクタを生成するために Thing の定義を必要とします。デストラクタを明示的に宣言し、その (空の) 実装を CPP ファイルに移動すると、コードがコンパイルされます。

于 2011-05-16T00:17:11.143 に答える
20

これは実装依存ではありません。それが機能する理由はshared_ptr、実行時に呼び出す正しいデストラクタを決定するためです。これは型シグネチャの一部ではありません。ただし、unique_ptrのデストラクタその型の一部であり、コンパイル時に認識されている必要があります。

于 2011-05-22T14:03:29.153 に答える
1

テンプレートのインスタンス化の時点で、モノの完全な定義が必要です。これが、pimpl イディオムがコンパイルされる正確な理由です。

それができなければ、人々はこのような質問をしないでしょう。

于 2011-05-22T13:57:18.053 に答える