Tの完全な定義を知るにはstd :: unique_ptr <T>が必要ですか?


248

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

#include <memory>

class Thing;

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

Thingタイプ定義を含まないcppにこのヘッダーを含めると、VS2010-SP1でコンパイルできません。

1> C:\ Program Files(x86)\ Microsoft Visual Studio 10.0 \ VC \ include \ memory(2067):error C2027:use of undefined type 'Thing'

置き換えstd::unique_ptrによってstd::shared_ptr、それがコンパイルされます。

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

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


20
C ++ 0xスマートポインターを使用して完全な型を作成する必要がある場合とそうでない場合の最も適切な説明は、ハワードヒナントの「不完全な型およびshared_ptr/ unique_ptr です。
James McNellis、

17
ポインタJamesをありがとう。そのテーブルをどこに置いたか忘れてしまいました!:-)
ハワードヒンナン、


5
@JamesMcNellisハワード・ヒナントのウェブサイトへのリンクがダウンしています。これがweb.archive.orgバージョンです。いずれにせよ、彼は同じ内容で以下に完全に答えました:-)
Ela782

もう1つの良い説明は、Scott MeyersのEffective Modern C ++のItem 22にあります。
Fred Schoen

回答:


328

ここから採用。

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

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

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

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

auto_ptr<A>上記の例で使用しても効果はありません。未加工のポインターを使用した場合と同じように、未定義の動作が発生します。

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

これ以上未定義の動作はありません:

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

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

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

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

しかし、それは参考になりました場合には、ここでのいくつかのメンバー文書化した表であるshared_ptrunique_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*}コンストラクタは不完全で逃げることができA、コンパイラはへの呼び出しを設定するために必要とされていない場合のみ~unique_ptr<A>()。たとえばunique_ptr、をヒープに置くと、不完全なを回避できますA。この点の詳細については、ここのBarryTheHatchetの回答を参照してください


3
すばらしい答えです。できれば+5にします。スマートポインターを最大限に活用しようとしている次のプロジェクトで、これを再び参照することになると思います。
Matthias

4
表の意味を説明できれば、より多くの人々に役立つと思います
ギタ

8
もう1つの注意:クラスコンストラクターは、そのメンバーのデストラクターを参照します(例外がスローされた場合、それらのデストラクターを呼び出す必要があります)。そのため、unique_ptrのデストラクタには完全な型が必要ですが、クラスにユーザー定義のデストラクタを持つだけでは不十分です。コンストラクタも必要です。
Johannes Schaub-litb

7
@Mehrdad:この決定は私の時間の前であるC ++ 98のためになされました。ただし、この決定は、実装可能性と仕様の難しさ(つまり、コンテナーのどの部分が完全な型を必要とするか、または必要としないか)の問題から生じたと思います。C ++ 98から15年の経験がある今日でも、この領域のコンテナー仕様を緩和し、重要な実装技術や最適化を禁止しないようにすることは容易なことではありません。できると思います。私が知っている、それは多くの作業になります。一人の試みが行われていることを知っています。
Howard Hinnant

9
上記のコメントからは明らかではないのでunique_ptr、クラスのメンバー変数としてを定義しているためにこの問題を抱えている人は、クラス宣言(ヘッダーファイル内)でデストラクタ(およびコンストラクタ)を明示的に宣言し、それらの定義に進んでくださいコンパイラーがヘッダーファイル内のコンストラクターまたはデストラクターを自動インライン化しないように(エラーをトリガーする)、ソースファイル内(およびソースファイル内のポイントされたクラスの完全な宣言と共にヘッダーを配置) stackoverflow.com/a/13414884/368896もこれを思い出させるのに役立ちます。
Dan Nissenbaum

42

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


5
これは、デフォルトの関数を使用する絶好の機会だと思います。MyClass::~MyClass() = default;実装ファイルでは、意図的に空白のままにするのではなく、デテクタ本体が消去されたと想定する誰かによって、後で誤って削除される可能性が低くなります。
Dennis Zickefoose、

@Dennis Zickefoose:残念ながらOPはVC ++を使用しており、VC ++はまだdefaultedおよびdeletedクラスメンバーをサポートしていません。
ildjarn、

6
ドアを.cppファイルに移動する方法の+1。また、それMyClass::~MyClass() = defaultをClangの実装ファイルに移動しないようです。(まだ?)
Eonil 2013

また、少なくともVS 2017では、コンストラクターの実装をCPPファイルに移動する必要があります。たとえば、この回答を参照してください:stackoverflow.com/a/27624369/5124002
jciloa

15

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


8

現在の答えは、デフォルトのコンストラクタ(またはデストラクタ)が問題である理由を正確に解明していないようですが、cppで宣言された空のコンストラクタは問題ではありません。

ここで何が起こっているのですか:

外部クラス(つまりMyClass)にコンストラクタまたはデストラクタがない場合、コンパイラはデフォルトのものを生成します。これに関する問題は、コンパイラが本質的にデフォルトの空のコンストラクタ/デストラクタを.hppファイルに挿入することです。つまり、デフォルトのコンストラクタ/デストラクタのコードは、ライブラリのバイナリではなく、ホスト実行可能ファイルのバイナリとともにコンパイルされます。ただし、この定義では部分クラスを実際に構成することはできません。そのため、リンカーがライブラリのバイナリに移動してコンストラクター/デストラクターを取得しようとすると、リンカーが見つからず、エラーが発生します。コンストラクタ/デストラクタコードが.cppにある場合、ライブラリバイナリはリンクに使用できます。

これは、unique_ptrまたはshared_ptrの使用とは関係ありません。他の回答は、unique_ptr実装の古いVC ++で混乱を招く可能性があるバグのようです(私のマシンではVC ++ 2015が正常に動作します)。

したがって、この話の教訓として、ヘッダーにはコンストラクタ/デストラクタの定義がないようにする必要があります。宣言のみを含めることができます。たとえば~MyClass()=default;、hppでは機能しません。コンパイラがデフォルトのコンストラクタまたはデストラクタを挿入できるようにすると、リンカーエラーが発生します。

もう1つの注意点:cppファイルにコンストラクターとデストラクターを配置してもこのエラーが引き続き発生する場合は、ライブラリが正しくコンパイルされていないことが原因であると考えられます。たとえば、VC ++でプロジェクトタイプをコンソールからライブラリに変更しただけで、VC ++が_LIBプリプロセッサシンボルを追加せず、まったく同じエラーメッセージを生成したため、このエラーが発生しました。


ありがとうございました!これは、信じられないほどあいまいなC ++の癖の非常に簡潔な説明でした。多くのトラブルを救いました。
JPNotADragon

5

完全を期すために:

ヘッダー:ああ

class B; // forward declaration

class A
{
    std::unique_ptr<B> ptr_;  // ok!  
public:
    A();
    ~A();
    // ...
};

ソースA.cpp:

class B {  ...  }; // class definition

A::A() { ... }
A::~A() { ... }

クラスBの定義は、コンストラクター、デストラクター、および暗黙的にBを削除する可能性のあるものすべてが見る必要があります(コンストラクターは上記のリストには表示されませんが、VS2017では、コンストラクターでもBの定義が必要です。これは、コンストラクタで例外が発生した場合、unique_ptrは再び破棄されます。)


1

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

それが不可能な場合、人々はこのような質問をしないでしょう。



-7

私としては、

QList<QSharedPointer<ControllerBase>> controllers;

ヘッダーを含めるだけ...

#include <QSharedPointer>

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