Enum kỳ lạ trong hàm hủy


83

Hiện tại, tôi đang đọc mã nguồn của Protocol Buffervà tôi tìm thấy một enummã lạ được xác định ở đây

  ~scoped_ptr() {
    enum { type_must_be_complete = sizeof(C) };
    delete ptr_;
  }

  void reset(C* p = NULL) {
    if (p != ptr_) {
      enum { type_must_be_complete = sizeof(C) };
      delete ptr_;
      ptr_ = p;
    }
  }

Tại sao cái enum { type_must_be_complete = sizeof(C) };được định nghĩa ở đây? Cái này được dùng để làm gì?


2
Nếu tôi muốn chắc chắn điều đó, thì tôi muốn sử dụng ptr_chính nó ở sizeofdạng sizeof(*ptr_)thay vì sizeof(C).
Nawaz

Câu trả lời:


81

Thủ thuật này tránh UB bằng cách đảm bảo rằng định nghĩa của C có sẵn khi trình hủy này được biên dịch. Nếu không, quá trình biên dịch sẽ không thành công vì sizeofkhông thể xác định kiểu không hoàn chỉnh (kiểu khai báo chuyển tiếp) nhưng con trỏ có thể được sử dụng.

Trong hệ nhị phân đã biên dịch, mã này sẽ được tối ưu hóa và sẽ không có hiệu lực.

Lưu ý rằng: Xóa loại không hoàn chỉnh có thể là hành vi không xác định từ 5.3.5 / 5 :.

nếu đối tượng bị xóa có loại lớp không hoàn chỉnh tại điểm xóa và lớp hoàn chỉnh có hàm hủy không tầm thường hoặc hàm phân bổ, thì hành vi là không xác định .

g++ thậm chí đưa ra cảnh báo sau:

cảnh báo: vấn đề có thể được phát hiện trong lệnh gọi của toán tử xóa:
cảnh báo: 'p' có
cảnh báo loại không đầy đủ : khai báo chuyển tiếp của 'struct C'


1
"Xóa loại không hoàn chỉnh là hành vi không xác định" là không chính xác. Nó chỉ UB nếu loại có một trình hủy không tầm thường. Vấn đề mà mẹo nhỏ này giải quyết chính xác là việc xóa loại không hoàn chỉnh không phải lúc nào cũng là UB để ngôn ngữ hỗ trợ nó.
Chúc mừng và hth. - Alf

Cảm ơn @ Cheersandhth.-Alf Quan điểm của tôi là nó có thể là UB, vì vậy nói chung dòng mã này là UB. Đã chỉnh sửa.
Mohit Jain,

32

sizeof(C)sẽ không thành công tại thời điểm biên dịch nếu Ckhông phải là một loại hoàn chỉnh. Đặt phạm vi cục bộ enumcho nó làm cho câu lệnh trở nên lành tính trong thời gian chạy.

Đó là một cách để lập trình viên tự bảo vệ mình khỏi chính họ: hành vi của một tiếp theo delete ptr_trên một kiểu không hoàn chỉnh là không xác định nếu nó có một trình hủy không tầm thường.


1
Bạn có thể giải thích tại sao C cần phải là một kiểu hoàn chỉnh ở điểm đó - có cần thiết phải có định nghĩa kiểu hoàn chỉnh để gọi deletenó không? Và nếu vậy, tại sao trình biên dịch vẫn không bắt được nó?
Peter Hull

1
Không phải là tránh C = voidsao? Nếu Cchỉ là một kiểu không xác định, không phải deletecâu lệnh đã bị lỗi?
Kerrek SB,

1
Có vẻ như Mohit Jain đã có câu trả lời.
Peter Hull

1
−1 "Đặt enum phạm vi cục bộ cho nó làm cho câu lệnh trở nên lành tính trong thời gian chạy." là, uh, vô nghĩa. Tôi xin lỗi.
Chúc mừng và hth. - Alf

1
@SteveJessop cảm ơn. Phần tôi đã thiếu là xóa một loại không hoàn chỉnh là UB.
Peter Hull

28

Để hiểu enum, hãy bắt đầu với việc xem xét hàm hủy mà không có nó:

~scoped_ptr() {
    delete ptr_;
}

ở đâu ptr_a C*. Nếu loại Clà không đầy đủ vào thời điểm này, tức là tất cả những gì trình biên dịch biết là struct C;, sau đó (1) một mặc định tạo làm-gì destructor được sử dụng cho các trường hợp C chỉ vào. Đó không phải là điều đúng đắn để làm đối với một đối tượng được quản lý bởi một con trỏ thông minh.

Nếu việc xóa thông qua con trỏ đến kiểu không hoàn chỉnh luôn có Hành vi không xác định, thì tiêu chuẩn có thể chỉ yêu cầu trình biên dịch chẩn đoán và không thành công. Nhưng nó được xác định rõ ràng khi trình hủy thực sự là tầm thường: kiến ​​thức mà lập trình viên có thể có, nhưng trình biên dịch không có. Tại sao ngôn ngữ định nghĩa và cho phép điều này là ngoài tôi, nhưng C ++ hỗ trợ nhiều phương pháp mà ngày nay không được coi là phương pháp tốt nhất.

Một kiểu hoàn chỉnh có kích thước đã biết và do đó, sizeof(C)sẽ biên dịch nếu và chỉ khi Clà một kiểu hoàn chỉnh - với hàm hủy đã biết. Vì vậy, nó có thể được sử dụng như một người bảo vệ. Một cách sẽ đơn giản là

(void) sizeof(C);  // Type must be complete

Tôi đoán rằng với một số trình biên dịch và các tùy chọn, trình biên dịch sẽ tối ưu hóa nó trước khi nó có thể nhận thấy rằng nó không nên biên dịch và đó enumlà một cách để tránh hành vi trình biên dịch không phù hợp như vậy:

enum { type_must_be_complete = sizeof(C) };

Một lời giải thích thay thế cho sự lựa chọn enumthay vì chỉ là một biểu thức bị loại bỏ, chỉ đơn giản là sở thích cá nhân.

Hoặc như James T. Hugget đề xuất trong một nhận xét cho câu trả lời này, "enum có thể là một cách tạo ra một thông báo lỗi giả di động tại thời điểm biên dịch".


(1) Bộ hủy không làm gì được tạo mặc định cho kiểu không hoàn chỉnh là một vấn đề với cũ std::auto_ptr. Nó xảo quyệt đến mức nó được đưa vào mục GOTW về thành ngữ PIMPL , được viết bởi chủ tịch hội đồng tiêu chuẩn hóa C ++ quốc tế Herb Sutter. Tất nhiên, ngày nay điều đó std::auto_ptrkhông còn được dùng nữa, thay vào đó người ta sẽ sử dụng một số cơ chế khác.


4
Enum có thể là một cách tạo thông báo lỗi giả di động tại thời điểm biên dịch.
Brice M. Dempsey

1
Tôi nghĩ rằng câu trả lời này giải thích động lực của mã rất tốt nhưng tôi muốn thêm rằng (1) một số trình biên dịch đã sizeof(T)đánh giá về 0 cho các loại không hoàn chỉnh thay vì biên dịch không thành công. Tuy nhiên, đây là hành vi không phù hợp. (2) Kể từ C ++ 11, sử dụng static_assert((sizeof(T) > 0), "T must be a complete type");sẽ là một giải pháp ưu việt (và thành ngữ).
5gon12eder

@ 5gon12eder; Vui lòng cung cấp một ví dụ về trình biên dịch C ++ có " sizeof(T)đánh giá về 0 cho các loại không hoàn chỉnh".
Chúc mừng và hth. - Alf

1
Tôi chưa bao giờ sử dụng một trình biên dịch như vậy và tôi sẽ không ngạc nhiên khi biết rằng chúng đã chết. Và ngay cả khi họ không làm như vậy, việc không quan tâm đến việc triển khai không phù hợp là một quyết định hợp lệ. Tuy nhiên, cả hai libstdc ++libc ++ sử dụng static_assert(sizeof(T) > 0, "…");trong việc triển khai tương ứng của họ std::unique_ptrđể đảm bảo loại là hoàn chỉnh ...
5gon12eder

1
… Nên tôi nghĩ có thể an toàn khi nói điều này là thành ngữ. Dù sao, vì đánh giá sizeof(T)trong bối cảnh boolean chính xác tương đương với thử nghiệm sizeof(T) > 0, nó không thực sự quan trọng ngoại trừ có thể vì lý do thẩm mỹ.
5gon12eder

3

Có thể một thủ thuật để chắc chắn Cđược xác định.

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.