C++ std::unique_ptr<T> 是否需要知道 T 的完整定义?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6012157/
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
Is std::unique_ptr<T> required to know the full definition of T?
提问by Klaim
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:
如果我在不包含Thing
类型定义的 cpp 中包含这个头文件,那么这不会在 VS2010-SP1 下编译:
1>C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\memory(2067): error C2027: use of undefined type 'Thing'
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.
替换std::unique_ptr
为std::shared_ptr
并编译。
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.
所以,我猜是当前 VS2010std::unique_ptr
的实现需要完整的定义,并且完全依赖于实现。
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?
或者是吗?它的标准要求中是否有某些内容使 的实现无法std::unique_ptr
仅使用前向声明?感觉很奇怪,因为它应该只持有一个指向 的指针Thing
,不是吗?
回答by Howard Hinnant
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 partialexceptions. Some, but not all of their members can be instantiated with incomplete types. The motivation for this is to support idioms such as pimplusing smart pointers, and without risking undefined behavior.
C++ 标准库中的大多数模板都要求使用完整类型实例化它们。但是shared_ptr
和unique_ptr
是部分例外。一些(但不是全部)成员可以用不完整的类型实例化。这样做的动机是使用智能指针支持诸如pimpl 之类的习语,并且不会冒未定义行为的风险。
Undefined behavior can occur when you have an incomplete type and you call delete
on it:
当您有一个不完整的类型并调用delete
它时,可能会发生未定义的行为:
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.
以上为合法代码。它会编译。您的编译器可能会也可能不会对上述代码发出警告。当它执行时,可能会发生不好的事情。如果你很幸运,你的程序会崩溃。然而,一个更可能的结果是你的程序会悄悄地泄漏内存,因为~A()
不会被调用。
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.
使用auto_ptr<A>
在上面的例子中并没有帮助。您仍然会得到与使用原始指针相同的未定义行为。
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.
尽管如此,在某些地方使用不完整的类是非常有用的!这是shared_ptr
和unique_ptr
帮助。使用这些智能指针之一将使您摆脱不完整的类型,除非有必要拥有完整的类型。最重要的是,当需要一个完整的类型时,如果你在那个时候尝试使用一个不完整类型的智能指针,你会得到一个编译时错误。
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();
// ...
};
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.
shared_ptr
并且unique_ptr
在不同的地方需要一个完整的类型。原因很模糊,与动态删除器与静态删除器有关。确切的原因并不重要。事实上,在大多数代码中,确切地知道需要完整类型的位置对您来说并不重要。只是代码,如果你弄错了,编译器会告诉你。
However, in case it is helpful to you, here is a table which documents several members of shared_ptr
and unique_ptr
with respect to completeness requirements. If the member requires a complete type, then entry has a "C", otherwise the table entry is filled with "I".
但是,如果这是对您有所帮助,这里是其中记录的几位成员表shared_ptr
,并unique_ptr
相对于完整性要求。如果成员需要完整类型,则条目为“C”,否则表条目为“I”。
Complete type requirements for unique_ptr and shared_ptr
unique_ptr shared_ptr
+------------------------+---------------+---------------+
| P() | I | I |
| default constructor | | |
+------------------------+---------------+---------------+
| P(const P&) | N/A | I |
| copy constructor | | |
+------------------------+---------------+---------------+
| P(P&&) | I | I |
| move constructor | | |
+------------------------+---------------+---------------+
| ~P() | C | I |
| destructor | | |
+------------------------+---------------+---------------+
| P(A*) | I | C |
+------------------------+---------------+---------------+
| operator=(const P&) | N/A | I |
| copy assignment | | |
+------------------------+---------------+---------------+
| operator=(P&&) | C | I |
| move assignment | | |
+------------------------+---------------+---------------+
| reset() | C | I |
+------------------------+---------------+---------------+
| reset(A*) | C | C |
+------------------------+---------------+---------------+
Any operations requiring pointer conversions require complete types for both unique_ptr
and shared_ptr
.
任何需要指针转换的操作都需要unique_ptr
和 的完整类型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'sanswer here.
该unique_ptr<A>{A*}
构造可以逃脱不完整A
仅如果不需要编译器建立一个呼叫~unique_ptr<A>()
。例如,如果你把unique_ptr
放在堆上,你可以逃脱不完整的A
. 关于这一点的更多细节可以在BarryTheHatchet 的回答中找到。
回答by Igor Nazarenko
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.
编译器需要 Thing 的定义来为 MyClass 生成默认的析构函数。如果您显式声明析构函数并将其(空)实现移动到 CPP 文件,代码应该可以编译。
回答by Puppy
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 ispart of its type, and it must be known at compile-time.
这与实现无关。它工作的原因是因为shared_ptr
确定了在运行时调用的正确析构函数 - 它不是类型签名的一部分。但是,unique_ptr
的析构函数是其类型的一部分,必须在编译时知道。
回答by Shital Shah
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.
看起来当前的答案并没有完全确定为什么默认构造函数(或析构函数)有问题,但在 cpp 中声明的空的不是。
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.
如果外部类(即 MyClass)没有构造函数或析构函数,则编译器生成默认的。问题在于编译器本质上是在 .hpp 文件中插入默认的空构造函数/析构函数。这意味着默认构造函数/析构函数的代码与主机可执行文件的二进制文件一起编译,而不是与库的二进制文件一起编译。然而,这个定义不能真正构造部分类。因此,当链接器进入您库的二进制文件并尝试获取构造函数/析构函数时,它没有找到任何内容并且您会收到错误消息。如果构造函数/析构函数代码在您的 .cpp 中,那么您的库二进制文件可用于链接。
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).
这与使用 unique_ptr 或 shared_ptr 无关,其他答案似乎可能是旧 VC++ 中用于 unique_ptr 实现的混淆错误(VC++ 2015 在我的机器上运行良好)。
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.
所以这个故事的寓意是你的头文件需要不受任何构造函数/析构函数定义的影响。它只能包含他们的声明。例如,~MyClass()=default;
在 hpp 中不起作用。如果允许编译器插入默认构造函数或析构函数,则会出现链接器错误。
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.
另一边注:如果即使在 cpp 文件中有构造函数和析构函数后仍然出现此错误,那么很可能是因为您的库没有被正确编译。例如,有一次我只是在 VC++ 中将项目类型从 Console 更改为 Library 并且我收到此错误,因为 VC++ 没有添加 _LIB 预处理器符号并且产生了完全相同的错误消息。
回答by Joachim
Just for completeness:
只是为了完整性:
Header: A.h
题主:啊
class B; // forward declaration
class A
{
std::unique_ptr<B> ptr_; // ok!
public:
A();
~A();
// ...
};
Source A.cpp:
来源 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.)
类 B 的定义必须被构造函数、析构函数和任何可能隐式删除 B 的东西看到。(虽然构造函数没有出现在上面的列表中,但在 VS2017 中,甚至构造函数也需要 B 的定义。考虑到这一点,这是有道理的如果构造函数中出现异常,则 unique_ptr 将再次销毁。)
回答by B?ови?
回答by deltanine
The simple answer is just use shared_ptr instead.
简单的答案是使用 shared_ptr 代替。
回答by Sanbrother
As for me,
至于我,
QList<QSharedPointer<ControllerBase>> controllers;
Just include the header ...
只需包含标题...
#include <QSharedPointer>