为什么 QSharedPointer:<T>:create 不完整对象的调用析构函数?

Why does QSharedPointer<T>::create call destructor of incomplete object?

本文关键字:对象 析构函数 调用 create lt QSharedPointer gt 为什么      更新时间:2023-10-16

我有以下代码示例:

#include <QCoreApplication>
#include <QSharedPointer>
#include <QDebug>
#include <memory>
class A
{
public:
    A()
    {
        throw 1;
    }
    ~A() { qDebug() << "A destr"; }
};
int main(int argc, char* argv[])
{
    QCoreApplication a(argc, argv);
    try
    {
        //auto m1 = std::make_shared<A>();
        auto m2 = QSharedPointer<A>::create();
    }
    catch (...)
    {
        qDebug() << "catch!";
    }
    return a.exec();
}

上述代码的输出为:

A destr
catch!

但是,如果我用std::make_shared取消注释该行,输出如下:

catch!

那么为什么QSharedPointer::create调用不完全对象的析构函数呢?是虫子还是我遗漏了什么?

我用MSVC2013+Qt 5.5.1MSVC2015+Qt 5.6(从源代码构建)进行了尝试。结果是一样的。

您似乎在Qt中发现了一个错误。我建议你提交一份错误报告,并参考这个有点相关的错误:https://bugreports.qt.io/browse/QTBUG-14637

问题似乎出在http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/tools/qsharedpointer_impl.h?h=v5.5.1#n420-其简化代码如下:

static inline QSharedPointer create()
{
    typedef QtSharedPointer::ExternalRefCountWithContiguousData<T> Private;
    typename Private::DestroyerFn destroy = &Private::deleter;
    QSharedPointer result(Qt::Uninitialized);
    result.d = Private::create(&result.value, destroy);
    new (result.data()) T();
    result.d->setQObjectShared(result.value, true);
    result.enableSharedFromThis(result.data());
    return result;
}

引用其他函数(大多在同一文件中)有点复杂,但在放置new调用构造函数之前,deleter似乎存储在result中。当构造函数抛出时,对象从未完全构造好,但QSharedPointer result已经构造好了,并且包含了deleter。从那里到deleter功能的短跳:

static void deleter(ExternalRefCountData *self)
{
    ExternalRefCountWithContiguousData *that =
            static_cast<ExternalRefCountWithContiguousData *>(self);
    that->data.~T();
}

现在你的析构函数被调用了,尽管你的构造函数从未完成。这是未定义的行为。如果你运气不好,这将破坏你的应用程序状态(因为这违反了只有在构造函数运行到完成时才调用析构函数的规则——一些类类型可能依赖这一规则)。

一个可能的解决方案(我还没有测试,但你可以)是:

static void noOpDeleter(ExternalRefCountData *self)
{
    Q_UNUSED(self);
}
static inline QSharedPointer create()
{
    typedef QtSharedPointer::ExternalRefCountWithContiguousData<T> Private;
    typename Private::DestroyerFn noDestroy = &noOpDeleter;
    typename Private::DestroyerFn destroy = &Private::deleter;
    QSharedPointer result(Qt::Uninitialized);
    result.d = Private::create(&result.value, noDestroy);
    new (result.data()) T();
    result.d->destroyer = destroy;
    result.d->setQObjectShared(result.value, true);
    result.enableSharedFromThis(result.data());
    return result;
}

如果你能验证以上内容,你应该可以将其编织成一个补丁,并将其提交给Qt bug跟踪器。希望有了工作补丁,他们会很快接受。

最后,我们将修复它!我猜应该是问题5.8.2或问题5.9。

谢谢@JohnZwinck,你的想法很好用。