我在factory.h中有一个工厂函数,它将std :: shared_ptr返回给foo.h中的基类。 factory.h使用前向声明到基类而不是包含foo.h.如下代码:
factory.h:
#include <memory>
// forward declaration
class foo;
std::shared_ptr<foo> create_foo_A(int A);
std::shared_ptr<foo> create_foo_B(int A, int B);
void work_with_foo(std::shared_ptr<foo> ptr);
在客户端代码中,如果使用nullptr初始化std :: shared_ptr到foo,编译器会发出警告。
main.cpp中:
#include "factory.h"
int main()
{
int type = 1;
std::shared_ptr<foo> ptr(nullptr); // <--- compile warning
if (type == 1)
ptr = create_foo_A(5566);
else
ptr = create_foo_B(5566, 7788);
work_with_foo(ptr);
return 0;
}
警告信息为:
warning C4150 : deletion of pointer to incomplete type 'foo'; no destructor called
这是合理的,因为std :: shared_ptr不知道foo的完整类型。如果main.cpp包含foo.h。
,则可以删除此警告但是如果使用非nullptr初始化std :: shared_ptr,编译就不会发出警告。 main.cpp中:
#include "factory.h"
int main()
{
int type = 1;
if (type == 1)
{
std::shared_ptr<foo> ptr = create_foo_A(5566); // <--- OK
work_with_foo(ptr);
}
else
{
std::shared_ptr<foo> ptr = create_foo_B(5566, 7788); // <--- OK
work_with_foo(ptr);
}
return 0;
}
在这个场景中,为什么std :: shared_ptr不需要知道类foo的完整类型?当std :: shared_ptr用nullptr和非null构造时,它是否非常不同?
答案 0 :(得分:3)
所以std::shared_ptr
在一个小整齐的包中包含三件事。
首先,它是一个指向某些数据的指针,使用覆盖可以让您访问它,就像使用原始C指针一样。
第二部分是参考计数器。它跟踪std::atomic<unsigned>
的等价物,即当它们进入/超出范围时,所有std::shared_ptr
复制掉第一个增量/减量。
第三部分是破坏函数 - 或更准确的指向破坏函数的指针。
从另一个复制std::shared_ptr
时,它使用引用计数器/销毁函数/指向源std::shared_ptr
的数据的指针。所以它不需要编写销毁函数,也不需要知道销毁函数的作用!
创建std::shared_ptr<Foo>
而不复制它时,必须创建销毁功能。默认销毁函数是调用Foo
的析构函数的函数:它需要Foo
的完整声明!
std::shared_ptr
, nullptr
也会调用销毁函数,因此即使在 empty 的情况下,也需要编写销毁函数
所以简短的故事是:在你创建std::shared_ptr<Foo>
的任何地方,不是通过复制或移动另一个std::shared_ptr
,你必须提供手动销毁功能,或者你必须Foo::~Foo
可见(即Foo
)的完整声明。
您调用返回std::shared_ptr
的函数的函数从函数体内复制它们:在该体内,std::shared_ptr
最终来自哪里,上面两个要求之一满足。
您只需在nullptr
上创建它就必须编写并存储销毁功能,并且需要完全访问该类。
如果您确实需要std::shared_ptr<foo> ptr(nullptr);
无法访问foo
,请尝试std::shared_ptr<foo> ptr(nullptr, [](foo*){});
,这应该传递无操作销毁功能。
答案 1 :(得分:3)
此:
std::shared_ptr<foo> ptr(nullptr);
不应警告。这是一个完全有效的陈述,无需知道foo
的完整类型。它可能会警告您,因为nullptr
正在被模拟。这也不应该警告:
std::shared_ptr<foo> ptr;
并且两者被指定为等效:
constexpr shared_ptr(nullptr_t) : shared_ptr() { }
在这个场景中,为什么std :: shared_ptr不需要知道类foo的完整类型?
create_foo_A
和create_foo_B
的定义需要知道foo
的完整类型。但声明没有。
简而言之,shared_ptr<T>::shared_ptr(U*)
构造函数需要知道U
的完整定义。但其他一切都没有。在这个答案中有一个更完整的调查:
https://stackoverflow.com/a/6089065/576911
所以如果你说的话:
std::shared_ptr<foo> ptr((foo*)0);
然后您有未定义的行为。显然VC ++会发出警告。 libc ++给出了一个很难的错误。而使用nullptr
是非常好的,至少根据C ++ 11。
总之,您的示例都不需要foo
的完整定义。
[util.smartptr.shared.dest]
~shared_ptr();
效果:
*this
为空或与另一个shared_ptr
实例(use_count() > 1
)共享所有权,则无副作用。[util.smartptr.shared.const]
constexpr shared_ptr() noexcept;
效果:构造一个空 shared_ptr
对象。
[util.smartptr.shared]
在概要中:
constexpr shared_ptr(nullptr_t) : shared_ptr() { }
答案 2 :(得分:1)
我担心你的问题完全倒退了。 : - )
在你调用create_foo_A()和create_foo_B()的第二个例子中,你基本上向编译器承诺,这些函数在链接到程序时将返回指向从foo派生的类的实例的指针。编译器不需要知道或关心构成foo的内容;在这一点上,由这些函数决定foo到底是什么。
但是在你的第一个例子中,行:
std::shared_ptr<foo> ptr(nullptr);
构造一个指向foo的共享指针,程序期望找到要调用的共享指针的析构函数。由于你还没有定义foo,只声明它是一个类,编译器不知道类是什么样的(它是一个不完整的类型)并且抱怨没有那个析构函数。