我有一个像这样的头文件foo.h
(省略了无关的东西):
#pragma once
#include <memory>
class Bar;
struct Foo
{
std::shared_ptr<Bar> getBar();
std::shared_ptr<const Bar> getBar() const
{
return const_cast<Foo*>(this)->getBar();
}
};
getBar()
的非常量重载是在.cpp文件中实现的,该文件也可以看到Bar
的完整定义。
当foo.h
包含在另一个文件中时(看不到Bar
的定义),VS 2010会给我一个警告:
warning C4150: deletion of pointer to incomplete type 'Bar'; no destructor called
在getBar()
的const重载上(或者实际上在从该重载实例化的标准库中的深处)。
我的问题是这个警告是否可以安全地被忽略。
我看待它的方式,在std::shared_ptr<Bar>
中调用了getBar() const
的两个成员函数:转换构造函数和析构函数。
// converting constructor
template <class Y>
std::shared_ptr<const Bar>::shared_ptr(std::shared_ptr<Y> &&r)
这用于从getBar() const
的返回值初始化getBar()
的返回值。这并未列出任何先决条件(C ++ 1127.2.2.1§20-22),这些先决条件需要Y
(在我的情况下为Bar
)才能完成。
// destructor
std::shared_ptr<const Bar>::~shared_ptr()
27.2.2.2§1规定当被销毁的共享指针为空时,没有副作用。
我理解为什么我会收到警告 - 析构函数代码也必须关心必须在存储指针上调用delete
时的情况,并且此代码确实会删除不完整的类型。但是我看到它的方式,在我的情况下永远无法达到,所以getBar() const
是安全的。
我是否正确,或者我是否忽略了某个电话或可能使getBar() const
实际删除不完整类型的内容?
答案 0 :(得分:7)
我找不到警告的理由。我也不能用clang / libc ++复制警告。
一般情况下,如果shared_ptr<Bar>
没有看到shared_ptr<Bar>
的构造需要Bar*
,并且可选择删除,则无法确定{{1}是永远被调用。无法知道~Bar()
中存储了哪些删除器,并且在shared_ptr<Bar>
旁边的d
中存储了一些未知的删除器shared_ptr<Bar>
(比如说Bar*
p
),不要求d(p)
致电~Bar()
。
例如,您的Bar
可能没有可访问的析构函数:
class Bar
{
~Bar();
};
您的Foo::getBar()
可以像这样实施:
std::shared_ptr<Bar>
Foo::getBar()
{
// purposefully leak the Bar because you can't call ~Bar()
return std::shared_ptr<Bar>(new Bar, [](Bar*){});
}
如果没有看到foo.cpp,编译器就无法知道。
此警告对我来说似乎是一个编译器错误,或者可能是std::shared_ptr
实现中的错误。
你能忽略这个警告吗?我不知道。在我看来,你正在处理实现中的错误,因此bug很可能意味着警告是真实的。但假设完全符合实现,我认为Bar
不需要在您显示的代码中成为完整类型。
答案 1 :(得分:2)
没有;不能安全地忽略警告。您的代码创建了一个shared_ptr对象。 shared_ptr构造函数是一个创建和存储删除器的模板。通过添加代码来在标题中创建shared_ptr,您就会过早地实例化模板构造函数。
shared_ptr使用的删除技巧允许您在定义类之前声明它们,但在首次使用它们之前,它们仍然需要查看完整类型。您的代码无法保证永远调用Bar的析构函数。更糟糕的是,今天,它甚至可以工作,但可能是定时炸弹,使你的代码中出现一个非常难以调试的错误。
问题与代码中指针的内容无关。只是事实上你有代码创建一个共享指针,它没有看到Bar的完整定义就足够了。
修复很简单。在完成Bar的声明之后,只需将该代码放入实现文件中。
编辑:g ++给出的警告符合这种情况,但代码却没有。现在我要留下这个答案,直到我有时间去调查(或者其他人弄清楚为什么g ++会发出这个警告)。