为什么 QSharedPointer<T>::create 调用不完整对象的析构函数?
Why does QSharedPointer<T>::create call destructor of incomplete object?
我有以下代码示例:
#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.1
和 MSVC2015
+ 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;
}
对其他函数的引用有点复杂(大多在同一个文件中),但在通过放置[=15=调用构造函数之前,deleter
似乎存储在result
中].当您的构造函数抛出时,您的对象永远不会完全构造,但 QSharedPointer result
已经构造,并包含删除器。从那里到 deleter
函数的一小步:
static void deleter(ExternalRefCountData *self)
{
ExternalRefCountWithContiguousData *that =
static_cast<ExternalRefCountWithContiguousData *>(self);
that->data.~T();
}
现在你的析构函数被调用了,尽管你的构造函数从未完成。那是未定义的行为。如果你不走运,这会破坏你的应用程序状态(因为它违反了只有在构造函数运行完成时才调用析构函数的规则——某些 class 类型可能依赖的规则)。
一个可能的解决方法(我还没有测试过,但你可以)是:
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 错误跟踪器。希望附上工作补丁后他们会及时接受它。
终于fixed!我想应该是 Qt 5.8.2 或 Qt 5.9。
谢谢@JohnZwinck,你的想法很好。
我有以下代码示例:
#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.1
和 MSVC2015
+ 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;
}
对其他函数的引用有点复杂(大多在同一个文件中),但在通过放置[=15=调用构造函数之前,deleter
似乎存储在result
中].当您的构造函数抛出时,您的对象永远不会完全构造,但 QSharedPointer result
已经构造,并包含删除器。从那里到 deleter
函数的一小步:
static void deleter(ExternalRefCountData *self)
{
ExternalRefCountWithContiguousData *that =
static_cast<ExternalRefCountWithContiguousData *>(self);
that->data.~T();
}
现在你的析构函数被调用了,尽管你的构造函数从未完成。那是未定义的行为。如果你不走运,这会破坏你的应用程序状态(因为它违反了只有在构造函数运行完成时才调用析构函数的规则——某些 class 类型可能依赖的规则)。
一个可能的解决方法(我还没有测试过,但你可以)是:
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 错误跟踪器。希望附上工作补丁后他们会及时接受它。
终于fixed!我想应该是 Qt 5.8.2 或 Qt 5.9。
谢谢@JohnZwinck,你的想法很好。