web-dev-qa-db-ja.com

Pimpl-不完全な型でmake_uniqueを呼び出すことができる理由

Make_unique呼び出しがコンパイルされるのはなぜですか? make_unqiueは、テンプレート引数が完全な型である必要はありませんか?

struct F;
int main()
{
  std::make_unique<F>();
}

struct F {};

質問元:私の "問題" PIMPL実装

デストラクタを実装クラス(PIMPL)のcppファイル内でユーザー宣言および定義する必要がある理由は理解しています。

しかし、pimpl-を含むクラスのコンストラクターを移動してもコンパイルされます。

class Object
{};

class CachedObjectFactory
{
public:
  CachedObjectFactory();

  ~CachedObjectFactory();
  std::shared_ptr<Object> create(int id) const;

private:
  struct CacheImpl;
  std::unique_ptr<CacheImpl> pImpl;
};

ここでcppファイル:

// constructor with make_unique on incompete type ?
CachedObjectFactory::CachedObjectFactory()
  : pImpl(std::make_unique<CacheImpl>())
{}

struct CachedObjectFactory::CacheImpl
{
  std::map<int, std::shared_ptr<Object>> idToObjects;
};

//deferred destructor
CachedObjectFactory::~CachedObjectFactory() = default;

これがコンパイルされる理由を誰かが説明できますか?なぜ建設と破壊に違いがあるのですか?デストラクタのインスタンス化とdefault_deleterのインスタンス化に問題がある場合、make_uniqueのインスタンス化に問題がないのはなぜですか。

26
AF_cpp

これがコンパイルされる理由は [temp.point]¶8 にあります:

関数テンプレート、メンバー関数テンプレート、またはクラステンプレートのメンバー関数または静的データメンバーの特殊化には、翻訳単位内に複数のインスタンス化ポイントがあり、上記のインスタンス化ポイントに加えて翻訳単位内にインスタンス化のポイントがあるそのような特殊化の場合、翻訳単位の終わりもインスタンス化のポイントと見なされます。クラステンプレートの特殊化翻訳単位内に最大で1つのインスタンス化ポイントがあります[...] 2つの異なるインスタンス化ポイントが1つの定義ルールに従ってテンプレートの特殊化に異なる意味を与える場合、プログラムは不正な形式であり、診断は必要ありません。

以下の編集で説明するので、この引用の終わりに注意してください。ただし、今のところ、OPのスニペットごとに実際に行われているのは、コンパイラーが追加で考慮されたインスタンス化を使用することです変換ポイントの最後に配置されるmake_unique()のポイント。これにより、コード内の元の使用ポイントでは定義が欠落することになります。仕様のこの節に従って、そうすることが許可されています。

これはもはやコンパイルされないことに注意してください:

struct Foo; int main(){ std::make_unique<Foo>(); } struct Foo { ~Foo() = delete; };

のように、コンパイラーはインスタンス化のポイントを見逃しません。テンプレートのコードを生成するために使用する翻訳単位のどの点でそれを延期するだけです。


編集:最後に、これらの複数のインスタンス化ポイントがある場合でも、定義がこれらのポイント間で異なる場合、動作が定義されているとは限りません。このdifferenceは、One Definition Ruleによって定義されている、上記の引用の最後の文に注意してください。これは、私のコメントから、@ hvdによる回答への直接の引用です。@ hvdは、これをここで明らかにしました。参照 One Definition Ruleのここ

すべてのプログラムには、破棄されたステートメントの外でそのプログラムでodr使用されるすべての非インライン関数または変数の正確に1つの定義が含まれます。診断は必要ありません。定義はプログラムで明示的に表示できます。標準ライブラリまたはユーザー定義ライブラリで見つけることができます。

OPの場合、2つのインスタンス化ポイントの違いは明らかです。@ hvd自身が指摘したように、最初のインスタンスは不完全なタイプで、2番目のインスタンスはそうではありません。実際、この違いは2つの異なる定義を構成しているので、このプログラムが不正な形式であることに疑いはほとんどありません。

13

make_uniqueには複数のインスタンス化ポイントがあります。翻訳単位の終わりもインスタンス化ポイントです。表示されているのは、CacheImpl/Fが完了すると、コンパイラはmake_uniqueのみをインスタンス化することです。コンパイラはこれを行うことができます。コードに依存している場合、コードの形式が正しくなく、コンパイラーがエラーを検出する必要はありません。

14.6.4.1インスタンス化のポイント[temp.point]

8関数テンプレート、メンバー関数テンプレート、またはクラステンプレートのメンバー関数または静的データメンバーの特殊化には、翻訳単位内に複数のインスタンス化ポイントがあり、上記のインスタンス化ポイントに加えて、翻訳単位内にインスタンス化のポイントがあるような特殊化では、翻訳単位の終わりもインスタンス化のポイントと見なされます。 [...]インスタンス化の2つの異なるポイントが、1つの定義規則(3.2)に従ってテンプレートの特殊化に異なる意味を与える場合、プログラムの形式は正しくなく、診断は必要ありません。

16
user743382