【问题标题】:shared_ptr destructor, copy and incomplete typeshared_ptr 析构函数,复制和不完整类型
【发布时间】:2014-10-13 20:03:16
【问题描述】:

我有一个像这样的头文件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&lt;Bar&gt; 的两个成员函数在getBar() const 中被调用:转换构造函数和析构函数。

// converting constructor
template <class Y>
std::shared_ptr<const Bar>::shared_ptr(std::shared_ptr<Y> &&r)

这用于从getBar()的返回值初始化getBar() const的返回值。这没有列出任何需要Y(在我的情况下为Bar)才能完成的先决条件(C++11 27.2.2.1 §20-22)。

// destructor
std::shared_ptr<const Bar>::~shared_ptr()

27.2.2.2 §1 规定当被销毁的共享指针为空时,没有副作用。

我理解为什么我会收到警告 - 析构函数代码还必须注意必须在存储的指针上调用 delete 的情况,并且此代码确实会删除不完整的类型。但在我看来,在我的情况下永远无法达到,所以getBar() const 是安全的。

我是正确的,还是我忽略了一个电话或其他可能使getBar() const 实际上删除不完整类型的东西?

【问题讨论】:

  • const_cast&lt;Foo*&gt;(this)-&gt;getBar();BAD 把戏 - 你最好从非常量版本调用 const 版本。
  • @ikh 我永远无法理解这个论点。就我而言,如果非常量版本修改了*this,那么你就完蛋了。在您的情况下,如果 const 版本返回一个指向 实际声明 const 的指针,那么您就完蛋了。两者都有一个失败点,你只需要一个额外的演员。
  • 在 const-calls-non-const 中,由类的用户确保不会在 const 对象上调用 const 版本。在非 const-calls-const 中,由类的创建者来确保逻辑有效。不要让你的课程成为没有检查你的逻辑的用户的炸弹。
  • @NeilKirk 我不认为它会给用户带来负担,它只是要求类的实现者不要在非常量重载中修改*this
  • 如果它不修改*this,那么函数应该是const。哎呀,现在你有两个 const 函数了!

标签: c++ c++11 shared-ptr incomplete-type


【解决方案1】:

我找不到警告的理由。我也不能用 clang/libc++ 复制警告。

一般来说,给定一个shared_ptr&lt;Bar&gt;,如果没有看到采用Bar*shared_ptr&lt;Bar&gt; 的构造以及可选的删除器,就无法确定~Bar() 是否是ever 调用。没有办法知道shared_ptr&lt;Bar&gt; 中存储了什么删除器,并且给定一些未知的删除器d 存储在shared_ptr&lt;Bar&gt; 中,在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 的实现中的错误。

你能忽略这个警告吗?我不知道。在我看来,您正在处理实现中的错误,因此该错误很可能意味着警告是真实的。但是假设一个完全符合的实现,我认为Bar 不需要在您显示的代码中是一个完整的类型。

【讨论】:

    【解决方案2】:

    没有;不能安全地忽略该警告。您的代码创建了一个 shared_ptr 对象。 shared_ptr 构造函数是创建和存储删除器的模板。通过在标头中添加代码来创建 shared_ptr,您最终会过早地实例化模板构造函数。

    shared_ptr 使用的删除器技巧允许你在定义类之前声明它们,但它们仍然需要在你第一次使用它们之前查看完整的类型。您的代码永远无法保证调用 Bar 的析构函数。更糟糕的是,今天它甚至可以工作,但可能是一个定时炸弹,使您的代码中出现一个非常难以调试的错误。

    问题与代码中指针的内容无关。只要您有创建共享指针的代码,它没有看到 Bar 的完整定义,这一事实就足够了。

    修复很简单。只需将该代码放入您的实现文件中,在 Bar 的完整声明之后。

    编辑:g++ 给出的警告适合这种情况,但代码不适合。现在我将保留这个答案,直到我有时间调查(或者其他人弄清楚为什么 g++ 会给出这个警告)。

    【讨论】:

    • 我不认为采用 shared_ptr 的构造函数实际上创建了一个删除器 - 它与参数共享所有权,因此它也必须使用它的删除器(或缺少它)。请注意,采用原始指针的 ctor 要求指向的类型是完整的,但采用 shared_ptr 的 ctor 则没有。
    • 我说的不是“采用 shared_ptr 的构造函数”——而是 shared_ptr 的构造函数本身。那是一个模板构造函数——它的工作方式类似于一个模板函数,因为它将调用参数与模板参数匹配,并根据它派生要使用的类型。一旦该模板匹配,它将实例化构造函数。该构造函数创建了删除器,而正是该删除器看到了不完整的类型。这正是 g++ 发出此警告的原因。
    • 但我只是在问题代码中从另一个shared_ptr 构造shared_ptr。链接中的重载 (10) 是否会创建删除器?
    • 不这么认为……至少在标准上是这样。是的,在我手机的薄界面中,看不到你在做什么。但这是 g++ 针对这种情况给出的警告,因此实现可能仍在尝试以某种方式实例化模板。
    • 我的工作理论是,在将 shared_ptr 转换为 shared_ptr 时,会为 const Bar 的 dtor 生成代码,但我要跳上飞机,所以目前无法调查。这是一个有趣的...我会回来查看的。
    猜你喜欢
    • 2013-07-13
    • 1970-01-01
    • 2018-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-15
    • 2012-03-18
    • 2017-01-08
    • 1970-01-01
    相关资源
    最近更新 更多