2009-11-30 76 views
37

Để khởi tạo thành viên tĩnh, tôi sử dụng cấu trúc trình trợ giúp lồng nhau, hoạt động tốt cho các lớp không được tạo khuôn mẫu. Tuy nhiên, nếu lớp bao quanh được tham số hóa bởi một khuôn mẫu, lớp khởi tạo lồng nhau không được khởi tạo, nếu đối tượng trợ giúp không được truy cập trong mã chính. Để minh hoạ, một ví dụ đơn giản (Trong trường hợp của tôi, tôi cần phải khởi tạo một vectơ).C++ Khởi tạo thành viên tĩnh (mẫu vui bên trong)

#include <string> 
#include <iostream> 

struct A 
{ 
    struct InitHelper 
    { 
     InitHelper() 
     { 
      A::mA = "Hello, I'm A."; 
     } 
    }; 
    static std::string mA; 
    static InitHelper mInit; 

    static const std::string& getA(){ return mA; } 
}; 
std::string A::mA; 
A::InitHelper A::mInit; 


template<class T> 
struct B 
{ 
    struct InitHelper 
    { 
     InitHelper() 
     { 
      B<T>::mB = "Hello, I'm B."; // [3] 
     } 
    }; 
    static std::string mB; 
    static InitHelper mInit; 

    static const std::string& getB() { return mB; } 
    static InitHelper& getHelper(){ return mInit; } 
}; 
template<class T> 
std::string B<T>::mB; //[4] 
template<class T> 
typename B<T>::InitHelper B<T>::mInit; 


int main(int argc, char* argv[]) 
{ 
    std::cout << "A = " << A::getA() << std::endl; 

// std::cout << "B = " << B<int>::getB() << std::endl; // [1] 
// B<int>::getHelper(); // [2] 
} 

Với g ++ 4.4.1:

  • [1] và [2] đã nhận xét:

    A = Hello, I'm A.

    trình như dự định

  • [1] không chú thích:

    A = Hello, I'm A. 
    B =

    tôi mong đợi, rằng InitHelper khởi MB

  • [1] và [2] không chú thích:
    A = Hello, I'm A. 
    B = Hello, I'm B.
    trình như dự định
  • [1] đã nhận xét, [2] không chú thích:
    segfault trong giai đoạn khởi tạo tĩnh tại [3]

Vì vậy, câu hỏi của tôi: Đây có phải là lỗi trình biên dịch hoặc lỗi nằm giữa màn hình và ghế không? Và nếu trường hợp sau là trường hợp: Có một giải pháp thanh lịch (nghĩa là không gọi phương thức khởi tạo tĩnh một cách rõ ràng) không?

Cập nhật I:
này có vẻ là một hành vi mong muốn (như được định nghĩa trong ISO/IEC chuẩn C++ 2003, 14.7.1):

Trừ khi một thành viên của một lớp mẫu hay một mẫu thành viên đã được thể hiện rõ ràng hoặc chuyên biệt một cách rõ ràng, việc chuyên môn hóa của thành viên được khởi tạo ngầm khi chuyên môn được tham chiếu trong một ngữ cảnh yêu cầu định nghĩa thành viên tồn tại; đặc biệt, khởi tạo (và bất kỳ tác dụng phụ liên quan) của một 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 được sử dụng theo cách yêu cầu định nghĩa thành viên dữ liệu tĩnh tồn tại.

+0

Visual Studio 2008 có cùng một hành vi (-gói trong khởi tạo tĩnh) –

+0

Tại sao bạn không viết std :: string B :: mB = "Xin chào, tôi là B."? –

+0

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. –

Trả lời

36

Đ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][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][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.
2
  • [1] Trường hợp không chú thích: Nó là ok. static InitHelper B<int>::mInit không tồn tại. Nếu thành viên của lớp mẫu (struct) không được sử dụng thì nó không được biên dịch.

  • [1] và [2] trường hợp chưa được phân loại: OK. B<int>::getHelper() sử dụng static InitHelper B<int>::mInitmInit tồn tại.

  • [1] đã nhận xét, [2] không được chú ý: nó hoạt động với tôi trong VS2008.

4

Vấn đề là các defintions bạn cung cấp cho các biến thành viên tĩnh cũng là các mẫu.

template<class T> 
std::string B<T>::mB; 
template<class T> 
typename B<T>::InitHelper B<T>::mInit; 

Trong quá trình biên dịch, điều này thực sự không có gì, vì T không được biết. Nó giống như một khai báo lớp hoặc một định nghĩa mẫu, trình biên dịch không tạo ra mã hoặc lưu trữ dự trữ khi nó nhìn thấy nó.

Định nghĩa xảy ra sau này, khi bạn sử dụng lớp mẫu. Bởi vì trong trường hợp segfaulting bạn không sử dụng B < int> :: mInit, nó không bao giờ được tạo ra.

Một giải pháp sẽ là explictly định thành viên cần thiết (mà không cần khởi tạo nó): Đặt ở đâu đó nguồn nộp

template<> 
typename B<int>::InitHelper B<int>::mInit; 

này hoạt động cơ bản theo cùng một cách như explictly xác định mẫu lớp.

+0

Tôi nghĩ, trong ví dụ này, nó sẽ không tạo sự khác biệt để đưa việc khởi tạo vào một tệp tiêu đề khác vì mọi thứ đều nằm trong cùng một đơn vị dịch thuật. –

+0

Tất nhiên. Đó là lý do tại sao tôi sử dụng thì có điều kiện ("sẽ"). Nhưng tôi không phải là người nói tiếng Anh bản xứ, vì vậy điều này là gây hiểu nhầm một cách hợp lý. Tôi đã xóa nó. – hirschhornsalz

+0

Vì vậy, bạn có hiểu tại sao bạn nhận được một thành viên segfault/empty, hoặc là nó vẫn chưa rõ ràng? – hirschhornsalz

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