2012-12-01 54 views
62

Trước khi C++ 11, chúng tôi chỉ có thể thực hiện khởi tạo trong lớp trên các thành viên const tĩnh của loại tích phân hoặc liệt kê. Stroustrup discusses this in his C++ FAQ, đưa ra các ví dụ sau:C++ 11 cho phép khởi tạo trong lớp các thành viên không tĩnh và không phải thành viên. Những gì đã thay đổi?

class Y { 
    const int c3 = 7;   // error: not static 
    static int c4 = 7;   // error: not const 
    static const float c5 = 7; // error: not integral 
}; 

Và lý do sau đây:

Vì vậy, tại sao những hạn chế bất tiện tồn tại? Một lớp thường được khai báo trong một tệp tiêu đề và một tệp tiêu đề thường được đưa vào nhiều đơn vị dịch. Tuy nhiên, để tránh các quy tắc liên kết phức tạp, C++ yêu cầu mọi đối tượng đều có một định nghĩa duy nhất. Quy tắc đó sẽ bị hỏng nếu C++ cho phép định nghĩa trong lớp các thực thể cần được lưu trữ trong bộ nhớ dưới dạng đối tượng.

Tuy nhiên, C++ 11 thư giãn những hạn chế này, cho phép trong lớp khởi tạo của các thành viên không tĩnh (§12.6.2/8):

Trong một constructor không ủy thác, nếu một thành viên hoặc lớp cơ sở dữ liệu không tĩnh không được chỉ định bởi mem-initializer-id (bao gồm cả trường hợp không có mem-initializer-list vì hàm tạo không có ctor-initializer) và thực thể không phải là lớp cơ sở ảo của lớp trừu tượng (10.4), sau đó là

  • nếu pháp nhân là thành viên dữ liệu không tĩnh có brace-hoặc-equal-initializer, pháp nhân được khởi tạo như được chỉ định trong 8.5;
  • nếu không, nếu thực thể là thành viên biến thể (9.5), thì không có khởi tạo nào được thực hiện;
  • nếu không, thực thể được khởi tạo mặc định (8.5).

Mục 9.4.2 cũng cho phép trong lớp khởi tạo của các thành viên tĩnh không const nếu họ được đánh dấu bằng constexpr specifier.

Vậy điều gì đã xảy ra với các lý do cho các hạn chế chúng tôi có trong C++ 03? Chúng ta chỉ đơn giản là chấp nhận các "quy tắc liên kết phức tạp" hoặc có một cái gì đó khác thay đổi mà làm cho điều này dễ dàng hơn để thực hiện?

+5

Không có gì xảy ra. Các trình biên dịch đã phát triển thông minh hơn với tất cả các mẫu chỉ tiêu đề này để phần mở rộng tương đối dễ dàng bây giờ. –

Trả lời

50

Câu trả lời ngắn gọn là họ giữ mối liên kết về cùng, với chi phí làm cho trình biên dịch vẫn phức tạp hơn trước đây.

I.e., thay vì điều này dẫn đến nhiều định nghĩa cho trình liên kết sắp xếp, nó vẫn chỉ dẫn đến một định nghĩa và trình biên dịch phải phân loại nó.

Điều này cũng dẫn đến các quy tắc hơi phức tạp hơn đối với lập trình viên để luôn được sắp xếp, nhưng đơn giản là đủ để không phải là vấn đề lớn. Các quy tắc thêm vào khi bạn có hai initializers khác nhau chỉ định cho một thành viên duy nhất:

class X { 
    int a = 1234; 
public: 
    X() = default; 
    X(int z) : a(z) {} 
}; 

Bây giờ, các quy tắc thêm vào thỏa thuận thời điểm này với những gì giá trị được sử dụng để khởi a khi bạn sử dụng các nhà xây dựng không mặc định.Câu trả lời cho điều đó khá đơn giản: nếu bạn sử dụng hàm tạo không chỉ định bất kỳ giá trị nào khác, thì 1234 sẽ được sử dụng để khởi tạo a - nhưng nếu bạn sử dụng hàm tạo chỉ định một số giá trị khác thì 1234 về cơ bản làm ngơ.

Ví dụ:

#include <iostream> 

class X { 
    int a = 1234; 
public: 
    X() = default; 
    X(int z) : a(z) {} 

    friend std::ostream &operator<<(std::ostream &os, X const &x) { 
     return os << x.a; 
    } 
}; 

int main() { 
    X x; 
    X y{5678}; 

    std::cout << x << "\n" << y; 
    return 0; 
} 

Kết quả:

1234 
5678 
+0

Dường như điều này hoàn toàn có thể xảy ra trước đây. Nó chỉ làm cho công việc viết một trình biên dịch khó hơn. Đó có phải là một tuyên bố công bằng không? – allyourcode

+3

@allyourcode: Có và không. Vâng, nó làm cho trình biên dịch trở nên khó hơn. Nhưng không, bởi vì nó * cũng * đã viết đặc tả C++ khá khó hơn một chút. –

6

Tôi đoán lý do mà có thể đã được viết trước khi mẫu đã được hoàn thành. Sau khi tất cả các "quy tắc liên kết phức tạp (s)" cần thiết cho khởi tạo trong lớp của các thành viên tĩnh là/đã cần thiết cho C++ 11 để hỗ trợ các thành viên tĩnh của các mẫu.

Cân nhắc

struct A { static int s = ::ComputeSomething(); }; // NOTE: This isn't even allowed, 
                // thanks @Kapil for pointing that out 

// vs. 

template <class T> 
struct B { static int s; } 

template <class T> 
int B<T>::s = ::ComputeSomething(); 

// or 

template <class T> 
void Foo() 
{ 
    static int s = ::ComputeSomething(); 
    s++; 
    std::cout << s << "\n"; 
} 

Vấn đề cho trình biên dịch là như nhau trong cả ba trường hợp: trong đó dịch đơn vị cần phát ra định nghĩa của s và mã cần thiết để khởi tạo nó? Giải pháp đơn giản là phát ra nó ở khắp mọi nơi và để cho trình liên kết sắp xếp nó ra. Đó là lý do tại sao những người liên kết đã hỗ trợ những thứ như __declspec(selectany). Nó sẽ không thể thực hiện C++ 03 nếu không có nó. Và đó là lý do tại sao nó không cần thiết để mở rộng mối liên kết.

Để đặt nó thẳng thắn hơn: Tôi nghĩ lý do được đưa ra trong tiêu chuẩn cũ chỉ đơn giản là sai.


CẬP NHẬT

Như Kapil chỉ ra, ví dụ đầu tiên của tôi là thậm chí không được cho phép trong tiêu chuẩn hiện hành (C++ 14). Tôi vẫn còn nó trong anyway, bởi vì nó IMO là trường hợp khó khăn nhất cho việc thực hiện (trình biên dịch, linker). Quan điểm của tôi là: ngay cả rằng trường hợp không khó hơn những gì đã được cho phép, ví dụ: khi sử dụng mẫu.

+0

Xấu hổ này đã không nhận được bất kỳ upvotes, như nhiều người trong số các C + + 11 tính năng tương tự trong đó trình biên dịch đã bao gồm các khả năng cần thiết hoặc tối ưu hóa. –

+0

@AlexCourt Tôi đã viết câu trả lời này gần đây. Câu hỏi và câu trả lời của Jerry là từ năm 2012. Vì vậy, tôi đoán đó là lý do tại sao câu trả lời của tôi không nhận được nhiều sự chú ý. –

+0

Điều này sẽ không tuân thủ "struct A {static int s = :: ComputeSomething();}" vì chỉ có const tĩnh có thể được khởi tạo trong lớp – Kapil

3

Về lý thuyết So why do these inconvenient restrictions exist?... lý do hợp lệ nhưng có thể dễ dàng bị bỏ qua và đây chính xác là những gì C++ 11 thực hiện.

Khi bạn bao gồm một tệp, nó chỉ bao gồm tệp và bỏ qua mọi lần khởi tạo. Các thành viên chỉ được khởi tạo khi bạn khởi tạo lớp học.

Nói cách khác, khởi tạo vẫn được gắn với hàm tạo, chỉ cần ký hiệu là khác nhau và thuận tiện hơn. Nếu hàm tạo không được gọi, các giá trị không được khởi tạo.

Nếu hàm tạo được gọi, các giá trị được khởi tạo với khởi tạo trong lớp nếu hiện tại hoặc hàm khởi tạo có thể ghi đè bằng cách khởi tạo riêng. Đường dẫn khởi tạo về cơ bản là giống nhau, có nghĩa là, thông qua hàm tạo.

Điều này hiển nhiên từ Stroustrup riêng FAQ trên C++ 11.

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