C++ 类型不完整的 std::unique_ptr 将无法编译

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9954518/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 13:26:00  来源:igfitidea点击:

std::unique_ptr with an incomplete type won't compile

c++unique-ptrincomplete-typelibc++

提问by Alexandre C.

I'm using the pimpl-idiom with std::unique_ptr:

我正在使用 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
};

However, I get a compile error regarding the use of an incomplete type, on line 304 in <memory>:

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

Invalid application of 'sizeof' to an incomplete type 'uixx::window::window_impl'

sizeof”对不完整类型“ uixx::window::window_impl”的无效应用

For as far as I know, std::unique_ptrshould be able to be used with an incomplete type. Is this a bug in libc++ or am I doing something wrong here?

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

回答by Alexandre C.

Here are some examples of std::unique_ptrwith incomplete types. The problem lies in destruction.

以下是std::unique_ptr不完整类型的一些示例。问题在于破坏。

If you use pimpl with unique_ptr, you need to declare a destructor:

如果使用 pimpl with 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
};

because otherwise the compiler generates a default one, and it needs a complete declaration of foo::implfor this.

因为否则编译器会生成一个默认的,它需要一个完整的声明foo::impl

If you have template constructors, then you're screwed, even if you don't construct the impl_member:

如果你有模板构造函数,那么即使你没有构造impl_成员,你也会被搞砸:

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

At namespace scope, using unique_ptrwill not work either:

在命名空间范围内, usingunique_ptr也不起作用:

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

since the compiler must know here how to destroy this static duration object. A workaround is:

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

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

回答by Fernando Costa Bertoldi

As Alexandre C.mentioned, the problem comes down to window's destructor being implicitly defined in places where the type of window_implis still incomplete. In addition to his solutions, another workaround that I've used is to declare a Deleter functor in the header:

正如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;
}

Note that using a custom Deleter function precludes the use of std::make_unique(available from C++14), as already discussed here.

请注意,使用自定义的 Deleter 函数排除了使用std::make_unique(可从 C++14 获得),正如这里已经讨论过的

回答by Walter

use a custom deleter

使用自定义删除器

The problem is that unique_ptr<T>must call the destructor T::~T()in its own destructor, its move assignment operator, and unique_ptr::reset()member function (only). However, these must be called (implicitly or explicitly) in several PIMPL situations (already in the outer class's destructor and move assignment operator).

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

As already pointed out in another answer, one way to avoid that is to move alloperations that require unique_ptr::~unique_ptr(), unique_ptr::operator=(unique_ptr&&), and unique_ptr::reset()into the source file where the pimpl helper class is actually defined.

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

However, this is rather inconvenient and defies the very point of the pimpl idoim to some degree. A much cleaner solution that avoids all that is to use a custom deleterand only move its definition into the source file where the pimple helper class lives. Here is a simple example:

然而,这相当不方便,并且在某种程度上违背了 pimpl idoim 的要点。一个更简洁的解决方案,它避免了使用自定义删除器的所有问题,并且只将其定义移动到 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; }

Instead of a separate deleter class, you can also use a free function or staticmember of fooin conjunction with a lambda:

除了单独的删除器类,您还可以将自由函数或static成员foo与 lambda 结合使用:

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

回答by adspx5

Probably you have some function bodies within .h file within class that uses incomplete type.

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

Make sure that within your .h for class window you have only function declaration. All function bodies for window must be in .cpp file. And for window_impl as well...

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

Btw, you have to explicitly add destructor declaration for windows class in your .h file.

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

But you CANNOT put empty dtor body in you header file:

但是您不能在头文件中放入空的 dtor 主体:

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

Must be just a declaration:

必须只是一个声明:

  class window {
    virtual ~window();
  }

回答by Matteo Italia

To add to the other's replies about the custom deleter, in our internal "utilities library" I added a helper header to implement this common pattern (std::unique_ptrof an incomplete type, known only to some of the TU to e.g. avoid long compile times or to provide just an opaque handle to clients).

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

It provides the common scaffolding for this pattern: a custom deleter class that invokes an externally-defined deleter function, a type alias for a unique_ptrwith this deleter class, and a macro to declare the deleter function in a TU that has a complete definition of the type. I think that this has some general usefulness, so here it is:

它为这个模式提供了通用的脚手架:一个调用外部定义的删除器函数的自定义删除器类,一个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

回答by Stepan Dyatkovskiy

May be not a best solution, but sometimes you may use shared_ptrinstead. If course it's a bit an overkill, but... as for unique_ptr, I'll perhaps wait 10 years more until C++ standard makers will decide to use lambda as a deleter.

可能不是最好的解决方案,但有时您可以使用shared_ptr代替。如果这当然有点矫枉过正,但是……至于 unique_ptr,我可能还要等 10 年,直到 C++ 标准制定者决定使用 lambda 作为删除器。

Another side. Per your code it may happen, that on destruction stage window_impl will be incomplete. This could be a reason of undefined behaviour. See this: Why, really, deleting an incomplete type is undefined behaviour?

另一边。根据您的代码,可能会发生在销毁阶段 window_impl 将不完整的情况。这可能是未定义行为的原因。看到这个: 为什么,真的,删除一个不完整的类型是未定义的行为?

So, if possible I would define a very base object to all your objects, with virtual destructor. And you're almost good. You just should keep in mind that system will call virtual destructor for your pointer, so you should define it for every ancestor. You should also define base class in inheritance section as a virtual (see thisfor details).

因此,如果可能的话,我会使用虚拟析构函数为所有对象定义一个非常基础的对象。你几乎好了。您只需要记住系统将为您的指针调用虚拟析构函数,因此您应该为每个祖先定义它。您还应该在继承部分将基类定义为虚拟(有关详细信息,请参阅)。