C++ 为什么';t std::shared#ptr需要知道完整类型,如果它是';s是由非null构造的?

C++ 为什么';t std::shared#ptr需要知道完整类型,如果它是';s是由非null构造的?,c++,c++11,C++,C++11,我在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, i

我在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);
#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;
}
#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;
}
警告信息是:

warning C4150 : deletion of pointer to incomplete type 'foo'; no destructor called
这是合理的,因为
std::shared_ptr
不知道foo的完整类型。如果main.cpp包含foo.h,则可以删除此警告

但是如果
std::shared_ptr
是用非
nullptr
初始化的,编译器不会发出警告

main.cpp

#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);
#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;
}
#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;
}
#包括“factory.h”
int main()
{
int型=1;
如果(类型==1)
{

std::shared_ptr ptr=create_foo_A(5566);//恐怕你的问题完全是反方向的。:-)

在第二个示例中,调用create_foo_A()和create_foo_B(),您实际上是在向编译器承诺,这些函数在链接到程序时,将返回指向从foo派生的类实例的指针。编译器不需要知道或关心foo的组成部分;此时,由这些函数决定什么是真正的foo

但在您的第一个示例中,行:

std::shared_ptr<foo> ptr(nullptr);
std::shared_ptr ptr(nullptr);

构造指向foo的共享指针,程序希望为要调用的共享指针找到析构函数。由于您尚未定义foo,只声明它是一个类,编译器不知道该类的外观(它是一个不完整的类型)并抱怨没有那个析构函数。

所以
std::shared\u ptr
是三件事,都包在一个整洁的小包裹里

首先,它是一个指向某些数据的指针,通过重写可以访问这些数据,这使得使用它与使用原始C指针非常相似

第二部分是参考计数器。它跟踪
std::atomic
的等价物,即所有
std::shared_ptr
在进入/离开范围时复制第一个增量/减量

第三部分是销毁函数——更确切地说,是指向所述销毁函数的指针

从另一个复制
std::shared_ptr
时,它使用引用计数器/销毁函数/指向源
std::shared_ptr
数据的指针。因此,它不需要编写销毁函数,也不需要知道销毁函数的功能

当您在不复制的情况下创建
std::shared_ptr
时,它必须创建销毁函数。默认销毁函数是调用
Foo
的析构函数的函数:它需要
Foo
的完整声明

std::shared_ptr
将调用销毁函数,即使指针是
nullptr
,因此即使在空的情况下,它也需要写入销毁函数

因此,简短的故事是:无论您在何处创建一个
std::shared_ptr
,而不是通过复制或移动另一个
std::shared_ptr
,您必须提供手动销毁功能,或者必须使
Foo::~Foo
可见(即
Foo
的完整声明)

调用返回
std::shared_ptr
的函数的情况是从函数体内部复制它们的:在该体中,无论那些
std::shared_ptr
最终来自何处,满足上述两个要求之一

在这种情况下,您只需在
nullptr
上创建它,它就必须编写并存储销毁函数,这需要完全访问该类

如果您确实需要
std::shared_ptr ptr(nullptr);
而不访问
foo
,请尝试
std::shared_ptr ptr(nullptr,[](foo*){});
,它应该传递一个不做任何事情的销毁函数。

std::shared_ptr<foo> ptr(nullptr);
并且这两个被指定为等效的:

constexpr shared_ptr(nullptr_t) : shared_ptr() { }
在这个场景中,为什么std::shared_ptr不需要知道类foo的完整类型

create\u foo\u A
create\u foo\u B
定义需要知道
foo
的完整类型,但声明不需要

简言之,
shared\U ptr::shared\U ptr(U*)
构造函数需要知道
U
的完整定义。但除此之外,几乎没有人知道。在这个答案中有一个更完整的调查:

所以如果你说:

std::shared_ptr<foo> ptr((foo*)0);

但是
shared\u ptr
已经知道了完整的类型-毕竟,它必须知道它才能在第一个位置存在该类型的对象。销毁代码绑定在创建第一个
shared\u ptr
的位置-在
create\u foo\u X
函数中。@Xeo:我想这是一个答案,不是注释!谢谢!我想我错过了这个链接。如果您将
共享的\u ptr
构造为空或带有
空ptr
,是否可以生成一个不应调用删除器的标准引用?