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 hằng số. Những gì đã thay đổi?


87

Trước C ++ 11, chúng ta 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 kiểu tích phân hoặc kiểu liệt kê. Stroustrup thảo luận về điều này trong Câu hỏi thường gặp về C ++ của mình , đưa ra ví dụ sau:

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:

Vậy tại sao lại tồn tại những hạn chế bất tiện này? Một lớp thường được khai báo trong tệp tiêu đề và 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 trình liên kết phức tạp, C ++ yêu cầu mọi đối tượng phải có một định nghĩa duy nhất. Quy tắc đó sẽ bị phá vỡ nếu C ++ cho phép định nghĩa trong lớp của 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 nới lỏng những hạn chế này, cho phép khởi tạo trong lớp các thành viên không tĩnh (§12.6.2 / 8):

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

  • nếu thực thể là thành viên dữ liệu không tĩnh có bộ khởi tạo dấu ngoặc nhọn hoặc dấu bằng , thì thực thể được khởi tạo như quy định trong 8.5;
  • ngược lại, nếu thực thể là một thành viên biến thể (9.5), thì không có quá trình 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).

Phần 9.4.2 cũng cho phép khởi tạo trong lớp các thành viên tĩnh không phải const nếu chúng được đánh dấu bằng mã constexprxác định.

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


5
Không có chuyện gì xảy ra. Các trình biên dịch đã trở nên thông minh hơn với tất cả các mẫu chỉ dành cho tiêu đề này, vì vậy đó là phần mở rộng tương đối dễ dàng hiện nay.
Öö Tiib

Điều thú vị là đủ trên IDE của tôi khi tôi chọn trước C ++ 11 biên soạn tôi được phép khởi tạo các thành viên const không tĩnh không thể thiếu
Dean P

Câu trả lời:


67

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

Tức là, thay vì điều này dẫn đến nhiều định nghĩa để 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 sắp xếp nó.

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

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

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

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

1
Có vẻ như điều này là khá khả thi 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?
mã allyourcode

10
@allyourcode: Có và không. Có, nó làm cho việc viết trình biên dịch khó hơn. Nhưng không, vì nó cũng khiến việc viết đặc tả C ++ khó hơn một chút.
Jerry Coffin,

Có sự khác biệt về cách khởi tạo thành viên lớp: int x = 7; hoặc int x {7};?
mbaros

9

Tôi đoán rằng lý do có thể đã được viết trước khi các mẫu được hoàn thiện. Sau khi tất cả "(các) quy tắc trình liên kết phức tạp" cần thiết cho các trình 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 mẫu.

Xem xét

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

Nói một cách thẳng thắn hơn: Tôi nghĩ lý do được đưa ra trong tiêu chuẩn cũ là hoàn toàn sai lầm.


CẬP NHẬT

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


Thật xấu hổ vì điều này không nhận được bất kỳ sự ủng hộ nào, vì nhiều tính năng của C ++ 11 tương tự ở chỗ các trình biên dịch đã bao gồm khả năng hoặc tối ưu hóa cần thiết.
Alex Court,

@AlexCourt Tôi đã viết câu trả lời này gần đây. Tuy nhiên, câu hỏi và câu trả lời của Jerry có 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ú ý.
Paul Groke

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

8

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

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

Nói cách khác, việc khởi tạo vẫn được gắn với hàm tạo, chỉ là ký hiệu là khác 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 phương thức khởi tạo được gọi, các giá trị sẽ được khởi tạo bằng khởi tạo trong lớp nếu có hoặc phương thức khởi tạo có thể ghi đè nó bằng khởi tạo riêng. Đường dẫn khởi tạo về cơ bản giống nhau, tức là thông qua hàm tạo.

Điều này được thể hiện rõ ràng từ Câu hỏi thường gặp của chính Stroustrup trên C ++ 11.


Re "Nếu hàm tạo không được gọi, các giá trị không được khởi tạo": Làm thế nào tôi có thể phá vỡ việc khởi tạo thành viên Y::c3trong câu hỏi? Theo tôi hiểu, nó c3sẽ luôn được khởi tạo trừ khi có một hàm tạo ghi đè mặc định được đưa ra trong khai báo.
Peter - Phục hồi Monica
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.