如果 std::shared_ptr 是从非 null 构造的,为什么它不需要知道完整的类型?
Why doesn't std::shared_ptr need to know complete type if it's constructed from non-null?
我在factory.h中有一个工厂函数,它将std::shared_ptr
返回到foo.h中的基类。factory.h使用基类的前向声明,而不是包含foo.h。作为以下代码:
工厂.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>
,编译器将发出警告。
主.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,则可以删除此警告。
但是如果std::shared_ptr
初始化为非nullptr
,编译器不会发出警告。
主.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
和非nullptr
构造时,它有很大的不同吗?
这个:
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() { }
所以std::shared_ptr
是三件事包装在一个整洁的小包装中。
首先,它是指向某些数据的指针,具有覆盖以允许您访问它,这使得使用它与使用原始 C 指针非常相似。
第二部分是参考计数器。 它跟踪相当于一个std::atomic<unsigned>
,当它们进入/超出范围时,所有std::shared_ptr
都会从第一个递增/递减中复制出来。
第三部分是破坏函数 - 或者更准确地说是指向所述破坏函数的指针。
当您从另一个std::shared_ptr
复制一个时,它使用引用计数器/销毁函数/指向源std::shared_ptr
数据的指针。 所以它不需要编写销毁函数,也不需要知道销毁函数是做什么的!
创建std::shared_ptr<Foo>
而不复制它时,它必须创建销毁函数。 默认的销毁函数是调用Foo
的析构函数:这需要完整声明Foo
!
即使指针nullptr
,std::shared_ptr
也会调用销毁函数,因此即使在空的情况下,它也需要编写该销毁函数。
所以简短的故事是:无论你在任何地方创建一个std::shared_ptr<Foo>
,而不是通过复制或从另一个std::shared_ptr
移动,你必须要么提供一个手动销毁功能,要么你必须有Foo::~Foo
可见的(即,Foo
的完整声明)。
调用返回std::shared_ptr
的函数的情况是从函数主体内部复制的:在该主体中,无论这些std::shared_ptr
最终来自何处,都满足上述两个要求之一。
您只需在nullptr
上创建它的情况,它必须编写并存储销毁函数,这需要对该类的完全访问权限。
如果您确实需要在无法访问foo
的情况下std::shared_ptr<foo> ptr(nullptr);
,请尝试std::shared_ptr<foo> ptr(nullptr, [](foo*){});
,它应该传递一个无所事事的破坏函数。
恐怕你的问题完全是倒退的。 :-)
在第二个示例中,您调用 create_foo_A() 和 create_foo_B(),您实际上是在向编译器承诺,这些函数在链接到程序中时,将返回指向派生自 foo 的类实例的指针。 编译器不需要知道或关心 foo 的组成; 在这一点上,由这些函数来决定 foo 到底是什么。
但是在您的第一个示例中,该行:
std::shared_ptr<foo> ptr(nullptr);
构造指向 foo 的共享指针,程序期望为要调用的共享指针找到析构函数。 由于您没有定义 foo,只声明它是一个类,编译器不知道该类是什么样子的(它是一个不完整的类型),并抱怨没有该析构函数。
- 为什么output_editor Concept不需要output_e迭代器标记
- 与C代码相比,为什么C++代码不需要"#define _POSIX_C_SOURCE 200809L"?
- 为什么转换函数声明不需要至少一个定义类型说明符
- 我不明白为什么我的代码不起作用并且需要更长的时间来运行
- 为什么python需要全局关键字而C/C++不需要?
- 为什么我们需要在 C++ 中检查空指针,而在 Java 中不需要?
- 为什么复制构造函数不需要检查输入对象是否指向自身?
- 为什么不需要在 C++20 中的依赖类型之前指定"typename"?
- 为什么 Mac 上不需要包含智能指针?
- 为什么MSVC(Visual C++)需要单独的dllimport和dllexport属性,而gcc不需要
- 我不明白为什么"Derived1"需要与"Derived3"相同的内存量
- 为什么在以下情况下不需要为依赖类型使用typename
- 为什么在调用C#DLL时不需要提供字符串缓冲区
- 为什么 pair 在初始化中不需要类型
- 为什么在VS2015中模板相关的嵌套类型名称中不需要typename关键字?
- 为什么在一个地方需要双倍英镑,而在这个宏观上不需要其他地方?
- 为什么嵌套类型的基类不需要"typename"?
- 为什么使用指向函数的指针调用虚函数时不需要指针"this"?
- 为什么我不需要在capnproto中使用AnyPointer::initAs?
- 如果不需要易失性,为什么 std::atomic 方法会提供易失性重载