具有不完整类型的std :: unique_ptr将无法编译

179 c++ unique-ptr incomplete-type libc++

我正在使用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
};
Run Code Online (Sandbox Code Playgroud)

但是,我在第304行的第304行收到有关使用不完整类型的编译错误<memory>:

' sizeof'到不完整类型' uixx::window::window_impl的应用无效' '

据我所知,std::unique_ptr应该可以使用不完整的类型.这是libc ++中的错误还是我在这里做错了什么?

Ale*_* C. 234

以下是一些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
};
Run Code Online (Sandbox Code Playgroud)

因为否则编译器会生成一个默认值,并且需要完整的声明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 !
}
Run Code Online (Sandbox Code Playgroud)

在命名空间范围内,使用unique_ptr将不起作用:

class impl;
std::unique_ptr<impl> impl_;
Run Code Online (Sandbox Code Playgroud)

因为编译器必须知道如何销毁这个静态持续时间对象.解决方法是:

class impl;
struct ptr_impl : std::unique_ptr<impl>
{
    ~ptr_impl(); // Implement (empty body) elsewhere
} impl_;
Run Code Online (Sandbox Code Playgroud)

  • 优秀的答案,请注意; 我们仍然可以通过在src文件中放置例如`foo :: ~foo()= default;`来使用默认的构造函数/析构函数 (28认同)
  • 我发现你的第一个解决方案(添加**foo**析构函数)允许类声明本身进行编译,但是在任何地方声明该类型的对象都会导致原始错误("sizeof'的无效应用程序......"). (3认同)
  • 与模板构造函数一起使用的一种方法是声明但不在类体中定义构造函数,在看到完整的impl定义的地方定义它,并在那里显式实例化所有必要的实例化. (2认同)
  • 你能解释一下这种情况在某些情况下会如何发挥作用而在其他情 我使用了带有unique_ptr的pimpl成语和没有析构函数的类,而在另一个项目中,我的代码无法使用提到的错误OP进行编译. (2认同)
  • 看来如果在c++11风格的类的头文件中将unique_ptr的默认值设置为{nullptr},出于上述原因也需要完整的声明。 (2认同)

Fer*_*ldi 42

正如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;
}
Run Code Online (Sandbox Code Playgroud)

  • 就我而言,这是正确的解决方案.使用pimpl-idiom并不是唯一的,使用std :: unique_ptr和不完整的类是一个普遍的问题.std :: unique_ptr <X>使用的默认删除器尝试执行"删除X",如果X是前向声明,则无法执行此操作.通过指定删除函数,您可以将该函数放在完全定义了类X的源文件中.然后其他源文件可以使用std :: unique_ptr <X,DeleterFunc>,即使X只是一个前向声明,只要它们与包含DeleterFunc的源文件链接即可. (3认同)

Wal*_*ter 15

使用自定义删除器

问题是unique_ptr<T>必须T::~T()在自己的析构函数,移动赋值运算符和unique_ptr::reset()成员函数(仅)中调用析构函数.但是,必须在几个PIMPL情境中调用(隐式或显式)(已经在外部类的析构函数和移动赋值运算符中).

作为另一个答案已经指出,要避免一个办法是将所有需要的操作unique_ptr::~unique_ptr(),unique_ptr::operator=(unique_ptr&&)以及unique_ptr::reset()为在平普尔辅助类实际上是定义的源文件.

然而,这是相当不方便的,并且在某种程度上违背了pimpl idoim的要点.一个更清晰的解决方案,避免所有使用自定义删除器,只将其定义移动到丘疹助手类所在的源文件.这是一个简单的例子:

// 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; }
Run Code Online (Sandbox Code Playgroud)

您也可以使用自由函数或static成员foo与lambda一起使用而不是单独的删除类:

class foo {
  struct pimpl;
  static void delete_pimpl(pimpl*);
  std::unique_ptr<pimpl,[](pimpl*ptr){delete_pimpl(ptr);}> m_pimpl;
};
Run Code Online (Sandbox Code Playgroud)

  • @Ivan_Bereziuk 是的,该代码是错误的。现在修好了。感谢您指出了这一点。 (2认同)

ads*_*px5 14

可能你在类中使用不完整类型的.h文件中有一些函数体.

确保在.h类窗口中只有函数声明.窗口的所有函数体必须位于.cpp文件中.而对于window_impl也是......

顺便说一下,你必须在.h文件中为windows类显式添加析构函数声明.

但是你不能把空的dtor体放在你的头文件中:

class window {
    virtual ~window() {};
  }
Run Code Online (Sandbox Code Playgroud)

必须只是一个声明:

  class window {
    virtual ~window();
  }
Run Code Online (Sandbox Code Playgroud)


Mat*_*lia 5

为了添加其他人关于自定义删除器的回复,在我们的内部“实用程序库”中,我添加了一个帮助程序标头来实现此常见模式(std::unique_ptr不完整类型,只有某些 TU 知道,例如避免较长的编译时间或提供只是对客户端的不透明句柄)。

它为这种模式提供了通用的脚手架:一个调用外部定义的删除器函数的自定义删除器类,一个具有unique_ptr此删除器类的类型别名,以及一个在 TU 中声明删除器函数的宏,该 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
Run Code Online (Sandbox Code Playgroud)