2014-11-13 16 views
12

Xét đoạn mã sau, nơi B là một lớp cơ sở ảo được thừa hưởng bởi D qua B1B2:Lỗi danh sách khởi tạo trong gcc?

#include <iostream> 

class B 
{ 
protected: 
    int x; 

protected: 

    B(int x) : x{x}{std::cout << x << std::endl;} 
}; 

class B1 : virtual public B 
{ 
protected: 

    B1() : B(0){} 
}; 

class B2 : virtual public B 
{ 
protected: 

    B2() : B(10){} 
}; 

class D : public B1, public B2 
{ 
public: 

    D() : B(99), B1(), B2() {} 
    void print() {std::cout << "Final: " << x << std::endl;} 
}; 

int main() { 
    D d; 
    d.print(); 
    return 0; 
} 

Xem ví dụ here làm việc. Tôi sử dụng kết quả đầu ra trong constructor của B và sau D đã được xây dựng hoàn chỉnh để theo dõi những gì đang xảy ra. Mọi thứ hoạt động tốt, khi tôi biên dịch ví dụ trên với g ++ - 4.8.1. Nó in

99 
Final: 99 

vì constructor B s được gọi là một lần từ các lớp có nguồn gốc nhất (D) và đó cũng xác định giá trị cuối cùng của x.

Bây giờ đến phần lạ: Nếu tôi thay đổi dòng

D() : B(99), B1(), B2() {} 

với cú pháp khởi tạo đồng phục mới, ví dụ:

D() : B{99}, B1{}, B2{} {} 

những điều kỳ lạ xảy ra. Thứ nhất, nó không biên dịch nữa, với các lỗi

prog.cpp: In constructor ‘D::D()’: 
prog.cpp:17:5: error: ‘B1::B1()’ is protected 
    B1() : B(0){} 
    ^
prog.cpp:31:27: error: within this context 
    D() : B{99}, B1{}, B2{} {} 

(và tương tự cho B2, xem here) mà không có ý nghĩa bởi vì tôi đang sử dụng nó trong một lớp học có nguồn gốc, vì vậy protected nên khỏe. Nếu tôi sửa cho điều đó và làm cho các nhà thầu của B1B2 công cộng thay vì bảo vệ, tất cả mọi thứ được hoàn toàn sai lầm (xem here), như sản lượng trở nên

99 
0 
10 
Final: 10 

Vì vậy, trên thực tế, các bộ phận của B1 s và Các nhà xây dựng của B2 s khởi tạo B vẫn được thực hiện và thậm chí thay đổi giá trị của x. Đây không phải là trường hợp thừa kế ảo. Và hãy nhớ, chỉ những điều tôi đã thay đổi

  • công cộng thay vì nhà xây dựng được bảo hộ tại B1B2
  • sử dụng classname{} cú pháp trong danh sách khởi tạo thành viên của D thay vì classname().

Tôi không thể tin rằng một điều cơ bản là sai trong gcc. Nhưng tôi đã thử nghiệm nó với tiếng kêu trên máy địa phương của tôi và ở đó, cả ba trường hợp biên dịch và chạy như dự định (ví dụ như ví dụ đầu tiên ở trên). Nếu nó không phải là một lỗi, ai đó có thể xin vui lòng chỉ cho tôi những gì tôi đang mất tích?

EDIT: tìm kiếm đầu tiên của tôi bằng cách nào đó đã không mang nó lên, nhưng bây giờ tôi tìm thấy this other question, hiển thị ít nhất là lỗi được bảo vệ/công cộng. Tuy nhiên, đây là gcc-4.7, vì vậy tôi đã mong đợi nó sẽ được xử lý trong gcc-4.8. Vì vậy, tôi nên kết luận các danh sách khởi tạo chỉ là sai lầm về cơ bản trong gcc !?

+2

này làm việc với kêu vang 3.5, nhưng không thành công với gcc 4.9.0. Vì vậy, đó có thể là lỗi trình biên dịch của ai đó. –

+0

Vâng, đó là khá nhiều những gì tôi đã nói trong câu hỏi của tôi, ngoại trừ việc nó dường như vẫn tồn tại ngay cả trong gcc-4.9.0! Điều làm tôi sợ là điều này có thể âm thầm dẫn đến nhiều lời gọi hàm tạo cho cùng một đối tượng (nếu tất cả các nhà xây dựng là công khai), vi phạm một trong những nguyên tắc cơ bản nhất của OOP ... – Oguk

+1

Tôi nhận được các lỗi giống hệt nhau trong GCC 5.1.1 '. – Galik

Trả lời

3

Tôi không biết nếu quá muộn để trả lời câu hỏi này, nhưng mã của bạn sẽ biên dịch tốt trong GCC 4.9.2!

~$g++ -std=c++11 test.cpp 
~$./a.out 
99 
Final: 99 

~$gcc --version 
gcc (GCC) 4.9.2 
Copyright (C) 2014 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
1

Về nhiều cuộc gọi của hàm tạo lớp cơ sở ảo: Tôi có thể tạo lại vấn đề với mã sau (với GCC 5.1.0).

#include <iostream> 

struct V { 
    V(){std::cout << "V()\n";} 
}; 

struct A : virtual V { 
    A() : V{} {std::cout << "A()\n";} 
}; 

struct B : A { 
    B(): V{}, A{} {std::cout << "B()\n";} 
}; 

int main(int argc, char **argv) { 
    B b{}; 
} 

Điều này dẫn đến kết quả như sau:

V() 
V() 
A() 
B() 

Tôi không nghĩ điều này là đúng accoring đến ++ tiêu chuẩn C:

[class.base.init]/7

... Việc khởi tạo được thực hiện bởi mỗi bộ khởi tạo mem tạo thành một biểu thức đầy đủ. Bất kỳ biểu thức nào trong bộ khởi tạo mem được đánh giá là một phần của biểu thức đầy đủ thực hiện khởi tạo. Bộ khởi tạo mem trong đó mem-initializer-id biểu thị một lớp cơ sở ảo bị bỏ qua trong khi thực hiện một hàm tạo của bất kỳ lớp nào không phải là lớp dẫn xuất nhất.

Khi cuộc gọi của hàm tạo A được thay đổi để sử dụng dấu ngoặc đơn thay vì dấu ngoặc, kết quả thực thi hoạt động như dự kiến ​​và chỉ gọi V() một lần.

tôi đã tạo ra một báo cáo lỗi cho GCC về vấn đề này: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70818

Edit: Tôi bị mất rằng có đã có được một báo cáo lỗi về vấn đề này: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55922

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