238

私はpimpl-idiomを使用していstd::unique_ptrます:

class window {
  window(const rectangle& rect);

private:
  class window_impl; // defined elsewhere
  std::unique_ptr<window_impl> impl_; // won't compile
};

ただし、不完全な型の使用に関するコンパイルエラーが発生します。304行目<memory>

sizeof不完全な型への''の無効な適用' uixx::window::window_impl'

私の知る限りstd::unique_ptr、不完全なタイプで使用できるはずです。これはlibc++のバグですか、それともここで何か間違ったことをしていますか?

4

7 に答える 7

312

std::unique_ptr不完全なタイプの例を次に示します。問題は破壊にあります。

でpimplを使用する場合はunique_ptr、デストラクタを宣言する必要があります。

class foo
{ 
    class impl;
    std::unique_ptr<impl> impl_;

public:
    foo(); // You may need a def. constructor to be defined elsewhere

    ~foo(); // Implement (with {}, or with = default;) where impl is complete
};

それ以外の場合、コンパイラはデフォルトを生成し、そのための完全な宣言が必要になるfoo::implためです。

テンプレートコンストラクターがある場合は、impl_メンバーを作成しなくても、失敗します。

template <typename T>
foo::foo(T bar) 
{
    // Here the compiler needs to know how to
    // destroy impl_ in case an exception is
    // thrown !
}

名前空間スコープでは、の使用も機能しunique_ptrません。

class impl;
std::unique_ptr<impl> impl_;

コンパイラは、この静的期間オブジェクトを破棄する方法をここで知っている必要があるためです。回避策は次のとおりです。

class impl;
struct ptr_impl : std::unique_ptr<impl>
{
    ~ptr_impl(); // Implement (empty body) elsewhere
} impl_;
于 2012-03-31T09:08:23.727 に答える
62

Alexandre C.が述べたように、問題は、のタイプがまだ不完全なwindow場所で暗黙的に定義されているのデストラクタに帰着します。window_impl彼の解決策に加えて、私が使用した別の回避策は、ヘッダーでDeleterファンクターを宣言することです。

// Foo.h

class FooImpl;
struct FooImplDeleter
{
  void operator()(FooImpl *p);
};

class Foo
{
...
private:
  std::unique_ptr<FooImpl, FooImplDeleter> impl_;
};

// Foo.cpp

...
void FooImplDeleter::operator()(FooImpl *p)
{
  delete p;
}

ここですでに説明したように、カスタムDeleter関数を使用すると、std::make_unique(C ++ 14から利用可能)を使用できなくなることに注意してください。

于 2013-07-30T14:56:15.250 に答える
28

カスタム削除機能を使用する

問題は、独自のデストラクタ、そのムーブ代入演算子、およびメンバー関数(のみ)でデストラクタをunique_ptr<T>呼び出さなければならないことです。ただし、これらは、いくつかのPIMPL状況(すでに外部クラスのデストラクタおよびムーブ代入演算子で)で(暗黙的または明示的に)呼び出す必要があります。T::~T()unique_ptr::reset()

別の回答ですでに指摘されているように、それを回避する1つの方法は、、、を必要とするすべての操作を、pimplヘルパークラスが実際に定義されているソースファイルに移動することです。unique_ptr::~unique_ptr()unique_ptr::operator=(unique_ptr&&)unique_ptr::reset()

ただし、これはかなり不便であり、pimplidoimの要点にある程度反します。カスタムデリッターを使用し、その定義をpimpleヘルパークラスが存在するソースファイルにのみ移動することをすべて回避する、はるかにクリーンなソリューション。簡単な例を次に示します。

// file.h
class foo
{
    struct pimpl;
    struct pimpl_deleter { void operator()(pimpl*) const; };
    std::unique_ptr<pimpl,pimpl_deleter> m_pimpl;
  public:
    foo(some data);
    foo(foo&&) = default;             // no need to define this in file.cc
    foo&operator=(foo&&) = default;   // no need to define this in file.cc
  //foo::~foo()          auto-generated: no need to define this in file.cc
};

// file.cc
struct foo::pimpl
{
  // lots of complicated code
};
void foo::pimpl_deleter::operator()(foo::pimpl*ptr) const { delete ptr; }

個別のdeleterクラスの代わりに、無料の関数またはstaticのメンバーをfooラムダと組み合わせて使用​​することもできます。

class foo {
    struct pimpl;
    struct deleter {
        operator()(pimpl*) const;
    };
    std::unique_ptr<pimpl,deleter> m_pimpl;
};
于 2015-08-28T10:52:53.100 に答える
17

おそらく、不完全な型を使用するクラス内の.hファイル内にいくつかの関数本体があります。

クラスウィンドウの.h内に、関数宣言のみがあることを確認してください。ウィンドウのすべての関数本体は、.cppファイルに含まれている必要があります。そしてwindow_implについても...

ところで、.hファイルにWindowsクラスのデストラクタ宣言を明示的に追加する必要があります。

ただし、ヘッダーファイルに空のdtor本文を入れることはできません。

class window {
    virtual ~window() {};
  }

単なる宣言である必要があります:

  class window {
    virtual ~window();
  }
于 2014-04-12T15:56:19.280 に答える
3

カスタム削除機能に関する他のユーザーの返信に追加するために、内部の「ユーティリティライブラリ」に、この一般的なパターンを実装するためのヘルパーヘッダーを追加しました(std::unique_ptr不完全なタイプで、一部のTUだけが知っているため、コンパイル時間が長くならないようにしたり、提供したりします。クライアントへの単なる不透明なハンドル)。

これは、このパターンに共通の足場を提供します。外部で定義された削除関数を呼び出すカスタム削除クラス、unique_ptrこの削除クラスを持つのタイプエイリアス、および完全な定義を持つTUで削除関数を宣言するマクロです。タイプ。これには一般的な有用性があると思うので、ここにあります:

#ifndef CZU_UNIQUE_OPAQUE_HPP
#define CZU_UNIQUE_OPAQUE_HPP
#include <memory>

/**
    Helper to define a `std::unique_ptr` that works just with a forward
    declaration

    The "regular" `std::unique_ptr<T>` requires the full definition of `T` to be
    available, as it has to emit calls to `delete` in every TU that may use it.

    A workaround to this problem is to have a `std::unique_ptr` with a custom
    deleter, which is defined in a TU that knows the full definition of `T`.

    This header standardizes and generalizes this trick. The usage is quite
    simple:

    - everywhere you would have used `std::unique_ptr<T>`, use
      `czu::unique_opaque<T>`; it will work just fine with `T` being a forward
      declaration;
    - in a TU that knows the full definition of `T`, at top level invoke the
      macro `CZU_DEFINE_OPAQUE_DELETER`; it will define the custom deleter used
      by `czu::unique_opaque<T>`
*/

namespace czu {
template<typename T>
struct opaque_deleter {
    void operator()(T *it) {
        void opaque_deleter_hook(T *);
        opaque_deleter_hook(it);
    }
};

template<typename T>
using unique_opaque = std::unique_ptr<T, opaque_deleter<T>>;
}

/// Call at top level in a C++ file to enable type %T to be used in an %unique_opaque<T>
#define CZU_DEFINE_OPAQUE_DELETER(T) namespace czu { void opaque_deleter_hook(T *it) { delete it; } }

#endif
于 2018-02-13T14:19:10.157 に答える
2

最善の解決策ではないかもしれませんが、代わりにshared_ptrを使用する場合があります。もちろんそれは少しやり過ぎですが...unique_ptrに関しては、C++標準メーカーがラムダを削除者として使用することを決定するまでおそらく10年以上待つでしょう。

別の側面。コードごとに、破棄段階でwindow_implが不完全になる可能性があります。これは、未定義の動作の理由である可能性があります。これを参照してください: なぜ、実際には、不完全な型を削除することは未定義の動作ですか?

したがって、可能であれば、仮想デストラクタを使用して、すべてのオブジェクトに対して非常に基本的なオブジェクトを定義します。そして、あなたはほとんど元気です。システムがポインタに対して仮想デストラクタを呼び出すことを覚えておく必要があります。したがって、すべての祖先に対して仮想デストラクタを定義する必要があります。また、継承セクションの基本クラスを仮想として定義する必要があります(詳細については、これを参照してください)。

于 2018-11-30T08:01:17.847 に答える
-1

使用するextern template

std::unique_ptr<T>where Tisが不完全なタイプを使用する場合の問題は、さまざまな操作unique_ptrのためにのインスタンスを削除できる必要があることです。Tクラスunique_ptrstd::default_delete<T>インスタンスを削除するために使用します。したがって、理想的な世界では

extern template class std::default_delete<T>;

std::default_delete<T>インスタンス化されないようにします。次に、宣言します

template class std::default_delete<T>;

T完了した場所で、テンプレートをインスタンス化します。

ここでの問題は、default_deleteインスタンス化されないインラインメソッドを実際に定義することです。したがって、このアイデアは機能しません。ただし、この問題を回避することはできます。

まず、呼び出し演算子をインライン化しない削除機能を定義しましょう。

/* --- opaque_ptr.hpp ------------------------------------------------------- */
#ifndef OPAQUE_PTR_HPP_
#define OPAQUE_PTR_HPP_

#include <memory>

template <typename T>
class opaque_delete {
public:
  void operator() (T* ptr);
};

// Do not move this method into opaque_delete, or it will be inlined!
template <typename T>
void opaque_delete<T>::operator() (T* ptr) {
  std::default_delete<T>()(ptr);
}

さらに、使いやすさのために、opaque_ptrと組み合わせるタイプを定義unique_ptropaque_delete、同様にstd::make_unique、を定義しますmake_opaque

/* --- opaque_ptr.hpp cont. ------------------------------------------------- */
template <typename T>
using opaque_ptr = std::unique_ptr<T, opaque_delete<T>>;

template<typename T, typename... Args>
inline opaque_ptr<T> make_opaque(Args&&... args)
{
  return opaque_ptr<T>(new T(std::forward<Args>(args)...));
}

#endif

このタイプopaque_deleteは、構造で使用できるようになりましたextern template。これが例です。

/* --- foo.hpp -------------------------------------------------------------- */
#ifndef FOO_HPP_
#define FOO_HPP_

#include "opaque_ptr.hpp"

class Foo {
public:
  Foo(int n);
  void print();
private:
  struct Impl;
  opaque_ptr<Impl> m_ptr;
};

// Do not instantiate opaque_delete.
extern template class opaque_delete<Foo::Impl>;

#endif

インスタンス化されないようにするためopaque_delete、このコードはエラーなしでコンパイルされます。リンカを幸せにするために、でインスタンス化opaque_deleteしますfoo.cpp

/* --- foo.cpp -------------------------------------------------------------- */

#include "foo.hpp"
#include <iostream>

struct Foo::Impl {
  int n;
};

// Force instantiation of opaque_delete.
template class opaque_delete<Foo::Impl>;

残りのメソッドは、次のように実装できます。

/* --- foo.cpp cont. -------------------------------------------------------- */
Foo::Foo(int n)
  : m_ptr(new Impl)
{
  m_ptr->n = n;
}

void Foo::print() {
  std::cout << "n = " << m_ptr->n << std::endl;
}

このソリューションの利点は、一度opaque_delete定義されると、必要な定型コードがかなり小さいことです。

于 2021-08-05T17:42:37.720 に答える