Trình biên dịch có thể đặt việc thực hiện một hàm hủy ảo được khai báo ngầm trong một đơn vị dịch riêng biệt không?


8

Đoạn mã sau sẽ biên dịch và liên kết với Visual Studio(cả 2017 và 2019 với /permissive-), nhưng không biên dịch với một trong hai gcchoặc clang.

foo.h

#include <memory>

struct Base {
    virtual ~Base() = default; // (1)
};

struct Foo : public Base {
    Foo();                     // (2)
    struct Bar;
    std::unique_ptr<Bar> bar_;
};

foo.cpp

#include "foo.h"

struct Foo::Bar {};            // (3)
Foo::Foo() = default;

main.cpp

#include "foo.h"

int main() {
    auto foo = std::make_unique<Foo>();
}

Sự hiểu biết của tôi là, trong main.cpp, Foo::Barphải là một loại hoàn chỉnh, bởi vì việc xóa nó được cố gắng ~Foo(), được khai báo ngầm và do đó được định nghĩa ngầm trong mọi đơn vị dịch thuật truy cập nó.

Tuy nhiên, Visual Studiokhông đồng ý và chấp nhận mã này. Ngoài ra, tôi thấy rằng các thay đổi sau đây sẽ Visual Studiotừ chối mã:

  • Làm cho (1)không ảo
  • Xác định (2)nội tuyến - tức là Foo() = default;hoặcFoo(){};
  • Đang gỡ bỏ (3)

Nó trông như thể Visual Studiotôi không định nghĩa một hàm hủy ngầm ẩn ở mọi nơi nó được sử dụng theo các điều kiện sau:

  • Hàm hủy ngầm định là ảo
  • Lớp này có một hàm tạo được định nghĩa trong một đơn vị dịch khác

Thay vào đó, nó dường như chỉ định nghĩa hàm hủy trong đơn vị dịch cũng chứa định nghĩa cho hàm tạo trong điều kiện thứ hai.

Vì vậy, bây giờ tôi đang tự hỏi:

  • Điều này có được phép không?
  • Nó được chỉ định ở bất cứ đâu, hoặc ít nhất được biết, đó là Visual Studiođiều này?

Cập nhật: Tôi đã gửi báo cáo lỗi https://developercommunity.visualstudio.com/content/probols/790224/implictly-declared-virtual-destructor-does-not-app.html . Hãy xem những gì các chuyên gia làm về điều này.


1
Điều gì xảy ra nếu bạn xây dựng mã với Visual Studio bằng công tắc / permissive- ?
Jesper Juhl

1
Cùng một kết quả. Tôi sẽ đặt nó trong câu hỏi.
Đánh dấu

1
Thay đổi 2 và 3 là rõ ràng, bạn cần một loại hoàn chỉnh khi deleter (mặc định) được gọi (trong hàm hủy của unique_ptr, một lần nữa xảy ra trong hàm tạo của Foo, vì vậy khi cái sau là nội tuyến, loại cần phải hoàn thành trong tiêu đề). Thay đổi 1 làm tôi ngạc nhiên, mặc dù, không có lời giải thích cho.
Aconcagua

Thêm phần này vào Foo: struct BarDeleter { void operator()(Bar*) const noexcept; };và thay đổi unique_ptr thành std::unique_ptr<Bar, BarDeleter> bar_;. Sau đó, trong đơn vị dịch thuật triển khai, hãy thêm vàovoid Foo::BarDeleter::operator()(Foo::Bar* p) const noexcept { try { delete p; } catch(...) {/*discard*/}}
Eljay

Câu trả lời:


2

Tôi tin rằng đây là một lỗi trong MSVC. Đối với std::default_delete::operator(), Tiêu chuẩn nói rằng [unique.ptr.dltr.dflt / 4] :

Lưu ý: Nếu T là loại không hoàn chỉnh, chương trình không được định dạng đúng .

Vì không mệnh đề "không cần chẩn đoán" , nên trình biên dịch C ++ tuân thủ được yêu cầu để đưa ra chẩn đoán [intro.compliance / 2.2] :

Nếu một chương trình có vi phạm bất kỳ quy tắc có thể chẩn đoán nào hoặc ..., việc triển khai tuân thủ sẽ đưa ra ít nhất một thông báo chẩn đoán .

cùng với [giới thiệu / tuân thủ / 1] :

Tập hợp các quy tắc có thể chẩn đoán bao gồm tất cả các quy tắc cú pháp và ngữ nghĩa trong tài liệu này ngoại trừ các quy tắc có chứa một ký hiệu rõ ràng rằng không có chẩn đoán nào được yêu cầu, hoặc được mô tả là dẫn đến hành vi không xác định được.


GCC sử dụng static_assertđể chẩn đoán tính đầy đủ của loại. MSVC dường như không thực hiện kiểm tra như vậy. Nếu nó âm thầm truyền tham số std::default_delete::operator()đến delete, thì điều này gây ra hành vi không xác định . Mà có thể tương ứng với quan sát của bạn. Nó có thể hoạt động, nhưng cho đến khi nó được đảm bảo bởi tài liệu (như một phần mở rộng C ++ không chuẩn), tôi sẽ không sử dụng nó.


Đây là lý do của tôi là tốt, cho đến nay.
Đánh dấu

1
@DanielLangr Ohhhh [@ $% * & +!] , Hoàn toàn bỏ qua, đó là mặc định constructor được cung cấp, không phải là destructor !!! Có một kẻ hủy diệt ảo trong lớp cơ sở đã đẩy tôi đi ... Xin lỗi . Bạn hoàn toàn đúng sau đó, tất nhiên. Tự hỏi bây giờ nếu việc cung cấp ctor thay vì dtor là cố tình hay vô tình ...
Aconcagua

1
@Aconcagua, đó là cố ý. Vấn đề là msvc định nghĩa hàm hủy ngầm định trong một đơn vị dịch thuật nơi nó không được sử dụng và không định nghĩa nó trong một đơn vị dịch thuật nơi nó được sử dụng.
Đánh dấu

1
@DanielLangr Cảm ơn vì liên kết ... Nhưng cũng có ý nghĩa đối với nhà xây dựng mặc định, chỉ cần xem xét một lớp có các thành viên phức tạp:class Demo { std::vector<int> data; };
Aconcagua

1
@Aconcagua Tôi nghĩ cuối cùng tôi cũng hiểu. Vấn đề không nằm ở hàm tạo mặc định của std::unique_ptr<Bar>. Vấn đề là với hàm tạo mặc định của Foo. Nếu có một ngoại lệ, Foo::Foo()cần phải hủy bỏ tiểu dự án đã được xây dựng bar_(rollback).
Daniel Langr
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.