1. ホーム
  2. c++

[解決済み】std::unique_ptr<T>はTの完全な定義を知るために必要なのでしょうか?

2022-04-01 19:03:23

質問

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

#include <memory>

class Thing;

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

このヘッダを含まないcppでインクルードすると Thing の型定義は、VS2010-SP1ではコンパイルできません。

1>C:♪Program Files (x86)♪マイクロソフト ビジュアルスタジオ 10.0VCVCludeincludememory(2067): error C2027: use of undefined type 'Thing'

交換 std::unique_ptrstd::shared_ptr と入力すると、コンパイルされます。

ということで、現在のVS2010の std::unique_ptr の実装で完全な定義が必要で、完全に実装依存です。

それとも、そうなのか?標準要件で std::unique_ptr の実装は、前方宣言のみで動作するのでしょうか?へのポインタを保持するだけでいいはずなのに、不思議な感じがします。 Thing ということでしょうか?

どのように解決するのですか?

からの採用 こちら .

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_ptrunique_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_ptrshared_ptr .

unique_ptr<A>{A*} コンストラクタは、不完全な A への呼び出しを設定する必要がない場合に限り、コンパイラは ~unique_ptr<A>() . 例えば、もしあなたが unique_ptr をヒープ上に置くと、不完全な A . この点に関する詳細は BarryTheHatchetの 回答 こちら .