2015-12-15 14 views
8

Tôi đã theo mã ví dụ:Tại sao QSharedPointer <T> :: tạo hủy cuộc gọi của đối tượng không đầy đủ?

#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(); 
} 

Các đầu ra cho các mã trên là:

A destr 
catch! 

Nhưng nếu tôi bỏ ghi chú phù hợp với std::make_shared đầu ra là như sau:

catch! 

Vậy tại sao QSharedPointer::create hủy cuộc gọi của đối tượng không đầy đủ? Đó có phải là một lỗi hoặc tôi đang thiếu một cái gì đó?

Tôi đã thử nó với MSVC2013 + Qt 5.5.1MSVC2015 + Qt 5.6 (được xây dựng từ các nguồn). Kết quả là như nhau.

+0

Để tham chiếu mã nguồn cho phiên bản 'QSharedPointer :: create() cụ thể của bạn là ở đây: http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/tools/ qsharedpointer_impl.h? h = v5.5.1 # n420 - Tôi cho rằng bạn đang xây dựng mà không có 'QT_SHAREDPOINTER_TRACK_POINTERS' được xác định? –

+0

Và đây là lỗi Qt có thể liên quan được đệ trình năm năm trước (không có hoạt động nào kể từ đó): https://bugreports.qt.io/browse/QTBUG-14637 –

+0

@JohnZwinck Có, nó không được xác định. Khi tôi hiểu lá cờ này được sử dụng để gỡ lỗi nhiều con trỏ thông minh sở hữu cùng một đối tượng. Hoặc tôi có nên thử không? – Yrchgrchh

Trả lời

5

Dường như bạn đã tìm thấy lỗi trong Qt. Tôi đề nghị quý vị nộp một báo cáo lỗi và tài liệu tham khảo này lỗi phần nào liên quan: https://bugreports.qt.io/browse/QTBUG-14637

Vấn đề có vẻ là trong http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/tools/qsharedpointer_impl.h?h=v5.5.1#n420 - mà đơn giản hóa mã trông như thế này:

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; 
} 

Đó là một chút phức tạp với tham chiếu đến các chức năng khác (chủ yếu trong cùng một tệp), nhưng có vẻ như deleter được lưu trữ trong result trước khi hàm tạo được gọi theo vị trí new. Khi constructor của bạn ném, đối tượng của bạn chưa bao giờ được xây dựng hoàn chỉnh, nhưng QSharedPointer result đã được xây dựng và chứa deleter. Từ đó, chỉ cần một bước nhảy ngắn đến hàm deleter:

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

Và giờ đây hàm hủy của bạn được gọi, mặc dù hàm tạo của bạn chưa bao giờ hoàn thành. Đó là hành vi không xác định. Nếu bạn không may mắn, điều này sẽ làm hỏng trạng thái ứng dụng của bạn (vì nó đi ngược lại quy tắc mà một destructor chỉ được gọi nếu một hàm tạo chạy đến hoàn thành - một quy tắc mà một số kiểu lớp có thể dựa vào).

Một sửa chữa càng tốt (mà tôi đã không kiểm tra, nhưng bạn có thể) là:

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; 
} 

Nếu bạn có thể xác nhận ở trên, bạn nên cảm thấy tự do để dệt nó thành một bản vá và nộp cho Trình theo dõi lỗi Qt. Hy vọng rằng với một miếng vá làm việc kèm theo họ sẽ chấp nhận nó kịp thời.

+0

Nó không mất may mắn: gọi destructor của một đối tượng mà không phải là sống là hành vi không xác định. Tuổi thọ của một đối tượng bắt đầu sau khi hàm tạo của nó hoàn tất thành công. Trong nhiều trường hợp, UB đó có thể vô hại, nhưng nó là UB bất kể. – Yakk

+0

@Yakk: Cảm ơn vì điều đó, tôi đã cập nhật câu trả lời của mình để phản ánh quan điểm của bạn. –

-1

Cuối cùng, chúng tôi sẽ có nó fixed! Tôi gues nó sẽ là Qt 5.8.2 hoặc Qt 5.9.

Cảm ơn @JohnZwinck, ý tưởng của bạn hoạt động tốt.

Các vấn đề liên quan