为什么std :: shared_ptr不需要知道完整类型,如果它是从非null构造的?

时间:2014-01-08 02:01:51

标签: c++ c++11

我在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构造时,它是否非常不同?

3 个答案:

答案 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_Acreate_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,只声明它是一个类,编译器不知道类是什么样的(它是一个不完整的类型)并且抱怨没有那个析构函数。