是 std::unique_ptr需要知道 T 的完整定义吗?

发布于 2024-11-08 02:37:38 字数 629 浏览 1 评论 0原文

我在标头中有一些代码,如下所示:

#include <memory>

class Thing;

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

如果我将此标头包含在不包含 Thing 类型定义的 cpp 中,则不会在 VS2010-SP1 下编译:

1>C:\Program Files (x86)\Microsoft 视觉工作室 10.0\VC\include\memory(2067): 错误 C2027: 使用未定义类型“Thing”

std::unique_ptr 替换为 std::shared_ptr 并编译。

所以,我猜测当前的 VS2010 std::unique_ptr 的实现需要完整的定义,并且它完全依赖于实现。

或者是吗?标准要求中是否存在某些内容使得 std::unique_ptr 的实现无法仅与前向声明一起使用?这感觉很奇怪,因为它应该只包含一个指向 Thing 的指针,不是吗?

I have some code in a header that looks like this:

#include <memory>

class Thing;

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

If I include this header in a cpp that does not include the Thing type definition, then this does not compile under VS2010-SP1:

1>C:\Program Files (x86)\Microsoft
Visual Studio
10.0\VC\include\memory(2067): error C2027: use of undefined type 'Thing'

Replace std::unique_ptr by std::shared_ptr and it compiles.

So, I'm guessing that it's the current VS2010 std::unique_ptr's implementation that requires the full definition and it's totally implementation-dependant.

Or is it? Is there something in it's standard requirements that makes impossible for std::unique_ptr's implementation to work with a forward declaration only? It feels strange as it should only hold a pointer to Thing, shouldn't it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

习惯成性 2024-11-15 02:37:38

摘自此处

C++ 标准库中的大多数模板都要求使用完整类型来实例化它们。然而,shared_ptrunique_ptr部分例外。它们的一些成员(但不是全部)可以用不完整的类型实例化。这样做的动机是使用智能指针支持 pimpl 等习惯用法,并且不会冒未定义行为的风险。

当您有不完整的类型并对其调用 delete 时,可能会发生未定义的行为:

class A;
A* a = ...;
delete a;

以上是合法代码。它将编译。您的编译器可能会也可能不会针对上述代码发出警告。当它执行时,可能会发生不好的事情。如果你很幸运,你的程序将会崩溃。然而,更可能的结果是您的程序将悄悄地泄漏内存,因为 ~A() 不会被调用。

在上面的示例中使用 auto_ptr 没有帮助。您仍然会得到与使用原始指针相同的未定义行为。

尽管如此,在某些地方使用不完整的类是非常有用的!这就是 shared_ptrunique_ptr 提供帮助的地方。使用这些智能指针之一将使您摆脱不完整类型的困扰,除非需要完整类型。最重要的是,当需要拥有完整类型时,如果您尝试在此时使用具有不完整类型的智能指针,则会出现编译时错误。

不再有未定义的行为

如果您的代码可以编译,那么您就已经在需要的任何地方使用了完整的类型。

class A
{
    class impl;
    std::unique_ptr<impl> ptr_;  // ok!

public:
    A();
    ~A();
    // ...
};

unique_ptrshared_ptr 的类型完整性要求

shared_ptrunique_ptr 在不同的地方需要完整的类型。原因很模糊,与动态删除器和静态删除器有关。确切的原因并不重要。事实上,在大多数代码中,准确了解哪里需要完整类型对您来说并不重要。只需编写代码,如果你写错了,编译器会告诉你。

不过,如果它对您有帮助,这里有一个表格,其中记录了 shared_ptrunique_ptr 相对于完整性要求的几个操作。

操作unique_ptrshared_ptr
P() (默认构造函数)不完整不完整
P(const P&) (复制构造函数)不完整
P(P&&)(移动构造函数)不完整不完整
~P()(析构函数)完整不完整
P(A*)(来自 ptr 的构造函数)不完整完整
< code>operator=(const P&) (复制赋值)不完整
operator=(P&&) (移动赋值)完整< /strong>不完整
reset()完整不完整
reset(A*)完整完整

任何需要指针转换的操作都需要 unique_ptrshared_ptr 的完整类型。

仅当编译器不需要设置对 ~unique_ptr的调用时,unique_ptr{A*} 构造函数才能摆脱不完整的 A 的影响。 A>()。例如,如果将 unique_ptr 放在堆上,则可能会得到不完整的 A。有关这一点的更多详细信息,请参阅 BarryTheHatchet 的回答 这里

Adopted from here.

Most templates in the C++ standard library require that they be instantiated with complete types. However shared_ptr and unique_ptr are partial exceptions. Some, but not all of their members can be instantiated with incomplete types. The motivation for this is to support idioms such as pimpl using smart pointers, and without risking undefined behavior.

Undefined behavior can occur when you have an incomplete type and you call delete on it:

class A;
A* a = ...;
delete a;

The above is legal code. It will compile. Your compiler may or may not emit a warning for above code like the above. When it executes, bad things will probably happen. If you're very lucky your program will crash. However a more probable outcome is that your program will silently leak memory as ~A() won't be called.

Using auto_ptr<A> in the above example doesn't help. You still get the same undefined behavior as if you had used a raw pointer.

Nevertheless, using incomplete classes in certain places is very useful! This is where shared_ptr and unique_ptr help. Use of one of these smart pointers will let you get away with an incomplete type, except where it is necessary to have a complete type. And most importantly, when it is necessary to have a complete type, you get a compile-time error if you try to use the smart pointer with an incomplete type at that point.

No more undefined behavior

If your code compiles, then you've used a complete type everywhere you need to.

class A
{
    class impl;
    std::unique_ptr<impl> ptr_;  // ok!

public:
    A();
    ~A();
    // ...
};

Type completeness requirements for unique_ptr and shared_ptr

shared_ptr and unique_ptr require a complete type in different places. The reasons are obscure, having to do with a dynamic deleter vs a static deleter. The precise reasons aren't important. In fact, in most code it isn't really important for you to know exactly where a complete type is required. Just code, and if you get it wrong, the compiler will tell you.

However, in case it is helpful to you, here is a table which documents several operations of shared_ptr and unique_ptr with respect to completeness requirements.

Operationunique_ptrshared_ptr
P() (default constructor)incompleteincomplete
P(const P&) (copy constructor)incomplete
P(P&&) (move constructor)incompleteincomplete
~P() (destructor)completeincomplete
P(A*) (constructor from ptr)incompletecomplete
operator=(const P&) (copy assignment)incomplete
operator=(P&&) (move assignment)completeincomplete
reset()completeincomplete
reset(A*)completecomplete

Any operations requiring pointer conversions require complete types for both unique_ptr and shared_ptr.

The unique_ptr<A>{A*} constructor can get away with an incomplete A only if the compiler is not required to set up a call to ~unique_ptr<A>(). For example if you put the unique_ptr on the heap, you can get away with an incomplete A. More details on this point can be found in BarryTheHatchet's answer here.

何以心动 2024-11-15 02:37:38

编译器需要 Thing 的定义来生成 MyClass 的默认析构函数。如果您显式声明析构函数并将其(空)实现移至 CPP 文件,则代码应该可以编译。

The compiler needs the definition of Thing to generate the default destructor for MyClass. If you explicitly declare the destructor and move its (empty) implementation to the CPP file, the code should compile.

荒芜了季节 2024-11-15 02:37:38

这不依赖于实现。它起作用的原因是 shared_ptr 确定在运行时调用的正确析构函数 - 它不是类型签名的一部分。但是,unique_ptr 的析构函数其类型的一部分,并且必须在编译时已知。

This isn't implementation-dependent. The reason that it works is because shared_ptr determines the correct destructor to call at run-time - it isn't part of the type signature. However, unique_ptr's destructor is part of its type, and it must be known at compile-time.

迷荒 2024-11-15 02:37:38

只是为了完整性:

标题:Ah

class B; // forward declaration

class A
{
    std::unique_ptr<B> ptr_;  // ok!  
public:
    A();
    ~A();
    // ...
};

Source A.cpp:

class B {  ...  }; // class definition

A::A() { ... }
A::~A() { ... }

类 B 的定义必须被构造函数、析构函数和任何可能隐式删除 B 的东西看到。
(虽然构造函数没有出现在上面的列表中,但在 VS2017 中,即使构造函数也需要 B 的定义。考虑到如果构造函数中出现异常,unique_ptr 会再次被销毁,这是有道理的。)

Just for completeness:

Header: A.h

class B; // forward declaration

class A
{
    std::unique_ptr<B> ptr_;  // ok!  
public:
    A();
    ~A();
    // ...
};

Source A.cpp:

class B {  ...  }; // class definition

A::A() { ... }
A::~A() { ... }

The definition of class B must be seen by constructor, destructor and anything that might implicitely delete B.
(Although the constructor doesn't appear in the list above, in VS2017 even the constructor needs the definition of B. And this makes sense when considering that in case of an exception in the constructor the unique_ptr is destroyed again.)

暖伴 2024-11-15 02:37:38

看起来当前的答案并没有完全确定为什么默认构造函数(或析构函数)是有问题的,但在 cpp 中声明的空构造函数却不是。

发生的情况如下:

如果外部类(即 MyClass)没有构造函数或析构函数,则编译器会生成默认的构造函数或析构函数。问题是编译器本质上是在 .hpp 文件中插入默认的空构造函数/析构函数。这意味着默认构造函数/析构函数的代码与主机可执行文件的二进制文件一起编译,而不是与库的二进制文件一起编译。然而这个定义并不能真正构造分部类。因此,当链接器进入库的二进制文件并尝试获取构造函数/析构函数时,它找不到任何构造函数/析构函数,并且您会收到错误。如果构造函数/析构函数代码位于您的 .cpp 中,则您的库二进制文件可用于链接。

这与使用 unique_ptr 或 shared_ptr 无关,其他答案似乎可能是旧 VC++ 中用于 unique_ptr 实现的令人困惑的错误(VC++ 2015 在我的机器上运行良好)。

所以这个故事的寓意是,你的标头需要保持没有任何构造函数/析构函数定义。它只能包含他们的声明。例如,hpp 中的 ~MyClass()=default; 将不起作用。如果允许编译器插入默认构造函数或析构函数,您将收到链接器错误。

另一方面注意:如果即使在 cpp 文件中有构造函数和析构函数之后您仍然收到此错误,那么很可能的原因是您的库未正确编译。例如,有一次我只是在 VC++ 中将项目类型从控制台更改为库,然后出现此错误,因为 VC++ 没有添加 _LIB 预处理器符号,并且产生了完全相同的错误消息。

It looks like current answers are not exactly nailing down why default constructor (or destructor) is problem but empty ones declared in cpp isn't.

Here's whats happening:

If outer class (i.e. MyClass) doesn't have constructor or destructor then compiler generates the default ones. The problem with this is that compiler essentially inserts the default empty constructor/destructor in the .hpp file. This means that the code for default contructor/destructor gets compiled along with host executable's binary, not along with your library's binaries. However this definitions can't really construct the partial classes. So when linker goes in your library's binary and tries to get constructor/destructor, it doesn't find any and you get error. If the constructor/destructor code was in your .cpp then your library binary has that available for linking.

This is nothing to do with using unique_ptr or shared_ptr and other answers seems to be possible confusing bug in old VC++ for unique_ptr implementation (VC++ 2015 works fine on my machine).

So moral of the story is that your header needs to remain free of any constructor/destructor definition. It can only contain their declaration. For example, ~MyClass()=default; in hpp won't work. If you allow compiler to insert default constructor or destructor, you will get a linker error.

One other side note: If you are still getting this error even after you have constructor and destructor in cpp file then most likely the reason is that your library is not getting compiled properly. For example, one time I simply changed project type from Console to Library in VC++ and I got this error because VC++ did not added _LIB preprocessor symbol and that produced exact same error message.

删除→记忆 2024-11-15 02:37:38

我正在寻找一种将 PIMPL 习惯用法与 std::unique_ptr 结合使用的方法。 本指南是一个很棒的资源。

简而言之,您可以执行以下操作来使其正常工作:

my_class.h

#include <memory>

class Thing;

class MyClass
{
    ~MyClass(); // <--- Added
    std::unique_ptr< Thing > my_thing;
};

my_class.cpp

MyClass::~MyClass() = default; // Or a custom implementation

I was looking for a way to use the PIMPL idiom with std::unique_ptr. This guide is a great resource.

In short, here's what you can do to make it work:

my_class.h

#include <memory>

class Thing;

class MyClass
{
    ~MyClass(); // <--- Added
    std::unique_ptr< Thing > my_thing;
};

my_class.cpp

MyClass::~MyClass() = default; // Or a custom implementation
翻身的咸鱼 2024-11-15 02:37:38

模板实例化时需要事物的完整定义。这就是 pimpl 惯用法编译的确切原因。

如果不可能,人们就不会问诸如这个之类的问题。

The full definition of the Thing is required at the point of template instantiation. This is the exact reason why the pimpl idiom compiles.

If it wasn't possible, people would not ask questions like this.

红墙和绿瓦 2024-11-15 02:37:38

简单的答案就是使用shared_ptr。

The simple answer is just use shared_ptr instead.

独夜无伴 2024-11-15 02:37:38

对于我来说,

QList<QSharedPointer<ControllerBase>> controllers;

只需包含标题...

#include <QSharedPointer>

As for me,

QList<QSharedPointer<ControllerBase>> controllers;

Just include the header ...

#include <QSharedPointer>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文