Điều này đã được thảo luận trên usenet một số thời gian trước đây, trong khi tôi đã cố gắng trả lời một câu hỏi khác trên stackoverflow: Point of Instantiation of Static Data Members. Tôi nghĩ rằng nó có giá trị giảm kiểm tra hợp cụ thể, và xem xét từng kịch bản trong sự cô lập, vì vậy chúng ta hãy nhìn vào nó tổng quát hơn đầu tiên:
struct C { C(int n) { printf("%d\n", n); } };
template<int N>
struct A {
static C c;
};
template<int N>
C A<N>::c(N);
A<1> a; // implicit instantiation of A<1> and 2
A<2> b;
Bạn có định nghĩa của một mẫu thành viên dữ liệu tĩnh. Điều này chưa tạo ra bất kỳ thành viên dữ liệu nào, vì 14.7.1
:
"đặc biệt, khởi tạo (và bất kỳ tác dụng phụ liên quan) nào của thành viên dữ liệu tĩnh không xảy ra trừ khi thành viên dữ liệu tĩnh chính nó được sử dụng theo cách yêu cầu định nghĩa của thành viên dữ liệu tĩnh tồn tại. "
Định nghĩa của cái gì đó (= thực thể) là cần thiết khi thực thể đó được "sử dụng", theo quy tắc định nghĩa một định nghĩa từ đó (tại 3.2/2
). Đặc biệt, nếu tất cả các tham chiếu đến từ các mẫu không được xác nhận, các thành viên của mẫu hoặc biểu thức sizeof
hoặc những thứ tương tự không "sử dụng" thực thể (vì chúng không có khả năng đánh giá nó hoặc chúng chưa tồn tại dưới dạng các hàm/các hàm thành viên được sử dụng chính nó), một thành phần dữ liệu tĩnh như vậy không được khởi tạo.
An instantiation by 14.7.1/7
instantiates khai báo của các thành viên dữ liệu tĩnh - đó là để nói, nó sẽ nhanh chóng bất kỳ mẫu cần thiết để xử lý tuyên bố đó. Nó sẽ không, tuy nhiên, nhanh chóng định nghĩa - đó là để nói, initializers không instantiated và constructors của loại thành viên dữ liệu tĩnh đó không được xác định ngầm (đánh dấu là sử dụng).
Đó là tất cả các phương tiện, mã ở trên sẽ không xuất được kết quả nào. Chúng ta hãy gây ra các instant instant ngầm của các thành viên dữ liệu tĩnh ngay bây giờ.
int main() {
A<1>::c; // reference them
A<2>::c;
}
Điều này sẽ khiến hai thành viên dữ liệu tĩnh tồn tại, nhưng câu hỏi đặt ra là - thứ tự khởi tạo như thế nào? Trên một đọc đơn giản, người ta có thể nghĩ rằng 3.6.2/1
áp dụng, mà nói (nhấn mạnh của tôi):
"Objects với thời gian lưu trữ tĩnh được định nghĩa trong phạm vi không gian tên trong cùng một đơn vị dịch và tự động khởi tạo sẽ được khởi tạo trong thứ tự trong đó định nghĩa của chúng xuất hiện trong đơn vị dịch."
Bây giờ như đã nói trong bài usenet và giải thích in this defect report, các thành viên dữ liệu tĩnh không được định nghĩa trong một đơn vị dịch thuật, nhưng họ được khởi tạo trong một đơn vị instantiation, như đã giải thích ở 2.1/1
:
Mỗi đơn vị dịch được dịch sẽ được kiểm tra để tạo ra một danh sách các cảnh báo cần thiết. [Lưu ý: điều này có thể bao gồm các phiên âm đã được yêu cầu rõ ràng (14.7.2).] Các định nghĩa về các mẫu được yêu cầu được đặt. nguồn của đơn vị dịch có chứa các định nghĩa này là r equired để có sẵn. [Lưu ý: việc triển khai có thể mã hóa đủ thông tin vào đơn vị dịch đã dịch để đảm bảo không cần nguồn ở đây. ] Tất cả các cảnh báo cần thiết được thực hiện để tạo ra các đơn vị instantiation. [Lưu ý: những điều này tương tự như các đơn vị dịch đã dịch, nhưng không chứa tham chiếu đến các mẫu không được phân tích và không có định nghĩa mẫu. ] Chương trình bị hỏng nếu xảy ra bất kỳ sự kiện nào.
The Point của õ của một thành viên đó cũng không thực sự quan trọng, bởi vì như vậy một điểm instantiation là liên kết bối cảnh giữa một instantiation và các đơn vị dịch của nó - nó xác định tờ khai mà có thể nhìn thấy (như quy định tại 14.6.4.1
và mỗi điểm trong số các điểm tức thời đó phải cung cấp cho các instantiations cùng một ý nghĩa, như được quy định trong một quy tắc định nghĩa tại 3.2/5
, dấu đầu dòng cuối cùng).
Nếu chúng ta muốn khởi tạo thứ tự, chúng ta phải sắp xếp để chúng ta không lộn xộn với instantiations, nhưng với khai báo rõ ràng - đây là khu vực chuyên môn rõ ràng, vì chúng không thực sự khác với khai báo bình thường. Trên thực tế, C++ 0x đã thay đổi từ ngữ 3.6.2
thành các từ sau:
Khởi động động đối tượng không phải cục bộ với thời gian lưu trữ tĩnh được sắp xếp hoặc không theo thứ tự. Định nghĩa về các lớp dữ liệu tĩnh mẫu chuyên biệt rõ ràng đã ra lệnh khởi tạo. Khác các thành phần dữ liệu tĩnh mẫu lớp (tức là, các chuyên ngành được tạo rõ ràng hoặc rõ ràng) có khởi tạo không theo thứ tự.
Điều này có nghĩa mã của bạn, rằng:
[1]
và [2]
nhận xét: Không có tài liệu tham khảo cho các thành viên dữ liệu tĩnh tồn tại, vì vậy định nghĩa của chúng (và cũng không kê khai , vì không cần phải khởi tạo B<int>
) không được khởi tạo. Không có tác dụng phụ xảy ra.
[1]
không được chú ý: B<int>::getB()
được sử dụng, tự sử dụng B<int>::mB
, yêu cầu thành viên tĩnh đó tồn tại. Chuỗi được khởi tạo trước chính (bất kỳ trường hợp nào trước câu lệnh đó, như là một phần của việc khởi tạo các đối tượng không phải cục bộ). Không có gì sử dụng B<int>::mInit
, do đó, nó không được khởi tạo và do đó không có đối tượng nào của B<int>::InitHelper
được tạo ra, điều này làm cho hàm tạo của nó không được sử dụng, do đó sẽ không bao giờ gán thứ gì đó cho B<int>::mB
: Bạn sẽ chỉ xuất ra một chuỗi rỗng.
[1]
và [2]
không chú thích: Đó này làm việc cho bạn là may mắn (hoặc ngược lại :)). Không có yêu cầu cho một thứ tự cụ thể của các cuộc gọi khởi tạo, như đã giải thích ở trên. Nó có thể làm việc trên VC++, thất bại trên GCC và làm việc trên clang. Chúng tôi không biết.
[1]
nhận xét, [2]
không chú thích: Cùng một vấn đề - một lần nữa, cả thành viên dữ liệu tĩnh là sử dụng: B<int>::mInit
được sử dụng bởi B<int>::getHelper
và instantiation của B<int>::mInit
sẽ gây ra constructor của nó được khởi tạo, mà sẽ sử dụng B<int>::mB
- nhưng đối với trình biên dịch của bạn, thứ tự là khác nhau trong lần chạy cụ thể này (hành vi không xác định là không nhất quán trong các lần chạy khác nhau): Nó khởi tạo B<int>::mInit
đầu tiên, sẽ hoạt động trên đối tượng chuỗi chưa được xây dựng.
Visual Studio 2008 có cùng một hành vi (-gói trong khởi tạo tĩnh) –
Tại sao bạn không viết std :: string B :: mB = "Xin chào, tôi là B."? –
Ok, tôi thấy rằng trong trường hợp thực tế bạn cần phải inialize một vector, xin lỗi. –