Định nghĩa các thành viên số nguyên const tĩnh trong định nghĩa lớp


108

Sự hiểu biết của tôi là C ++ cho phép các thành viên const tĩnh được định nghĩa bên trong một lớp miễn là nó là kiểu số nguyên.

Vậy tại sao đoạn mã sau đây lại cho tôi lỗi trình liên kết?

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

Lỗi tôi nhận được là:

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

Thật thú vị, nếu tôi nhận xét lệnh gọi tới std :: min, mã biên dịch và liên kết tốt (mặc dù test :: N cũng được tham chiếu ở dòng trước).

Bất kỳ ý tưởng về những gì đang xảy ra?

Trình biên dịch của tôi là gcc 4.4 trên Linux.


3
Hoạt động tốt trên Visual Studio 2010.
Puppy.

4
Lỗi chính xác này được giải thích tại gcc.gnu.org/wiki/…
Jonathan Wakely

Trong trường hợp cụ thể của char, bạn có thể định nghĩa nó thay thế bằng constexpr static const char &N = "n"[0];. Lưu ý &. Tôi đoán điều này hoạt động vì các chuỗi ký tự được xác định tự động. Tuy nhiên, tôi hơi lo lắng về điều này - nó có thể hoạt động kỳ lạ trong tệp tiêu đề giữa các đơn vị dịch khác nhau, vì chuỗi có thể sẽ ở nhiều địa chỉ khác nhau.
Aaron McDaid,

1
Câu hỏi này là biểu hiện của câu trả lời C ++ cho "không sử dụng #defines cho hằng số" vẫn còn kém như thế nào.
Johannes Overmann,

1
@JohannesOvermann Về vấn đề này, tôi muốn đề cập đến việc sử dụng nội tuyến cho các biến toàn cục kể từ C ++ 17 inline const int N = 10, theo hiểu biết của tôi vẫn có một bộ lưu trữ ở đâu đó được xác định bởi trình liên kết. Nội tuyến từ khóa cũng có thể được sử dụng trong trường hợp này để cung cấp định nghĩa biến tĩnh bên trong kiểm tra định nghĩa lớp.
Wormer

Câu trả lời:


72

Sự hiểu biết của tôi là C ++ cho phép các thành viên const tĩnh được định nghĩa bên trong một lớp miễn là nó là kiểu số nguyên.

Bạn đúng là như vậy. Bạn được phép khởi tạo tích phân const tĩnh trong khai báo lớp nhưng đó không phải là định nghĩa.

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr038.htm

Thật thú vị, nếu tôi nhận xét lệnh gọi tới std :: min, mã biên dịch và liên kết tốt (mặc dù test :: N cũng được tham chiếu ở dòng trước).

Bất kỳ ý tưởng về những gì đang xảy ra?

std :: min nhận tham số của nó bằng tham chiếu const. Nếu nó lấy chúng theo giá trị thì bạn không gặp vấn đề này nhưng vì bạn cần tham khảo nên bạn cũng cần định nghĩa.

Đây là chương / câu:

9.4.2 / 4 - Nếu một phần tử staticdữ liệu thuộc kiểu consttích phân hoặc constkiểu liệt kê, thì khai báo của nó trong định nghĩa lớp có thể chỉ định bộ khởi tạo hằng là biểu thức hằng tích phân (5.19). Trong trường hợp đó, phần tử có thể xuất hiện trong các biểu thức hằng số tích phân. Thành viên sẽ vẫn được xác định trong phạm vi không gian tên nếu nó được sử dụng trong chương trình và định nghĩa phạm vi không gian tên sẽ không chứa bộ khởi tạo .

Hãy xem câu trả lời của Chu để biết cách giải quyết khả thi.


Tôi hiểu, điều đó thật thú vị. Trong trường hợp đó, sự khác biệt giữa cung cấp giá trị tại điểm khai báo so với cung cấp giá trị tại điểm xác định là gì? Cái nào được khuyến khích?
HighCommander 4

Tôi tin rằng bạn có thể bỏ qua mà không cần định nghĩa miễn là bạn không bao giờ thực sự "sử dụng" biến. Nếu bạn chỉ sử dụng nó như một phần của biểu thức hằng thì biến không bao giờ được sử dụng. Nếu không, dường như không có sự khác biệt lớn ngoài việc bạn có thể nhìn thấy giá trị trong tiêu đề - có thể có hoặc không phải những gì bạn muốn.
Edward Strange

2
Câu trả lời ngắn gọn là static const x = 1; là một rvalue nhưng không phải là một lvalue. Giá trị có sẵn dưới dạng hằng số tại thời điểm biên dịch (bạn có thể kích thước một mảng với nó) static const y; [không có trình khởi tạo] phải được xác định trong tệp cpp và có thể được sử dụng dưới dạng rvalue hoặc lvalue.
Dale Wilson

2
Sẽ rất tốt nếu họ có thể mở rộng / cải thiện điều này. Theo ý kiến ​​của tôi, các đối tượng được khởi tạo nhưng không được định nghĩa phải được xử lý giống như các nghĩa đen. Ví dụ, chúng ta được phép liên kết một chữ 5với a const int&. Vì vậy, tại sao không coi OP test::Nlà nghĩa đen tương ứng?
Aaron McDaid,

Lời giải thích thú vị, cảm ơn! Điều này có nghĩa là trong C ++ static const int vẫn không thay thế cho số nguyên #defines. enum luôn chỉ được ký int, vì vậy người ta phải sử dụng các lớp enum cho các hằng số riêng lẻ. Đối với tôi, sẽ khá rõ ràng là suy biến một khai báo hằng với các giá trị hằng và biết thành một hằng số theo nghĩa đen theo cách mà điều này sẽ biên dịch mà không gặp vấn đề gì. C ++ có một chặng đường dài để đi ...
Johannes Overmann

51

Ví dụ của Bjarne Stroustrup trong Câu hỏi thường gặp về C ++ của anh ấy cho thấy bạn đúng và chỉ cần một định nghĩa nếu bạn lấy địa chỉ.

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

Anh ta nói "Bạn có thể lấy địa chỉ của một thành viên tĩnh nếu (và chỉ khi) nó có một định nghĩa ngoài lớp" . Điều đó cho thấy nó sẽ hoạt động theo cách khác. Có thể hàm min của bạn gọi các địa chỉ bằng cách nào đó đằng sau hậu trường.


2
std::minlấy các tham số của nó bằng cách tham chiếu, đó là lý do tại sao cần có định nghĩa.
Rakete1111

Tôi sẽ viết định nghĩa như thế nào nếu AE là lớp mẫu AE <lớp T> và c7 không phải là int mà là T :: size_type? Tôi có giá trị được khởi tạo thành "-1" trong tiêu đề nhưng clang nói giá trị không xác định và tôi không biết cách viết định nghĩa.
Fabian

@Fabian Tôi đang đi du lịch và nghe điện thoại và hơi bận ... nhưng tôi nghĩ rằng bình luận của bạn có vẻ như nó sẽ được viết dưới dạng một câu hỏi mới. Viết MCVE bao gồm lỗi bạn gặp phải, cũng có thể đưa ra những gì gcc nói. Tôi cá là mọi người sẽ nhanh chóng cho bạn biết những gì là gì.
HostileFork nói không tin tưởng SE

@HostileFork: Khi viết MCVE, đôi khi bạn tự tìm ra giải pháp. Đối với trường hợp của tôi, câu trả lời là template<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;nơi KeyContainer là một typedef của std :: vector <K>. Người ta phải liệt kê tất cả các tham số mẫu và viết tên kiểu vì đó là kiểu phụ thuộc. Có lẽ ai đó sẽ thấy bình luận này hữu ích. Tuy nhiên, bây giờ tôi tự hỏi làm thế nào để xuất tệp này trong một DLL vì lớp mẫu tất nhiên nằm trong tiêu đề. Có cần xuất c7 không ???
Fabian

24

Một cách khác để làm điều này, đối với các kiểu số nguyên, là xác định các hằng số dưới dạng các enum trong lớp:

class test
{
public:
    enum { N = 10 };
};

2
Và điều này có thể sẽ giải quyết vấn đề. Khi N được sử dụng làm tham số cho min (), nó sẽ tạo ra một biến tạm thời thay vì cố gắng tham chiếu đến một biến được cho là đã tồn tại.
Edward Strange

Điều này có lợi thế là nó có thể được đặt ở chế độ riêng tư.
Agostino

11

Không chỉ int. Nhưng bạn không thể xác định giá trị trong khai báo lớp. Nếu bạn có:

class classname
{
    public:
       static int const N;
}

trong tệp .h thì bạn phải có:

int const classname::N = 10;

trong tệp .cpp.


2
Tôi biết rằng bạn có thể khai báo một biến thuộc bất kỳ kiểu nào bên trong khai báo lớp. Tôi đã nói rằng tôi nghĩ rằng các hằng số nguyên tĩnh cũng có thể được định nghĩa bên trong khai báo lớp. đây không phải là trường hợp? Nếu không, tại sao trình biên dịch không đưa ra lỗi tại dòng mà tôi cố gắng xác định nó bên trong lớp? Hơn nữa, tại sao dòng std :: cout không gây ra lỗi trình liên kết, nhưng dòng std :: min thì có?
HighCommander 4

Không, không thể xác định các thành viên tĩnh trong khai báo lớp vì quá trình khởi tạo tạo ra mã. Không giống như một hàm nội tuyến cũng phát ra mã, một định nghĩa tĩnh là duy nhất trên toàn cầu.
Amardeep AC9MF

@ HighCommander4: Bạn có thể cung cấp bộ khởi tạo cho static constthành viên tích phân trong định nghĩa lớp. Nhưng điều đó vẫn không xác định thành viên đó. Xem câu trả lời của Noah Roberts để biết chi tiết.
AnT

9

Đây là một cách khác để giải quyết vấn đề:

std::min(9, int(test::N));

(Tôi nghĩ câu trả lời của Crazy Eddie mô tả chính xác lý do tại sao vấn đề tồn tại.)


5
hoặc thậm chístd::min(9, +test::N);
Tomilov Anatoliy

Đây là câu hỏi lớn mặc dù: tất cả những điều này có tối ưu không? Tôi không biết các bạn thế nào, nhưng điểm thu hút lớn của tôi khi bỏ qua định nghĩa là nó sẽ không chiếm bộ nhớ và không tốn chi phí khi sử dụng const static.
Opux

6

Kể từ C ++ 11, bạn có thể sử dụng:

static constexpr int N = 10;

Về mặt lý thuyết, điều này vẫn yêu cầu bạn xác định hằng số trong tệp .cpp, nhưng miễn là bạn không lấy địa chỉ của Nnó thì rất ít khả năng việc triển khai trình biên dịch sẽ tạo ra lỗi;).


Và điều gì sẽ xảy ra nếu bạn cần truyền giá trị dưới dạng đối số kiểu 'const int &' như trong ví dụ? :-)
Wormer

Điều đó hoạt động tốt. Bạn không khởi tạo N theo cách đó, chỉ chuyển một tham chiếu const đến một tham chiếu tạm thời. wandbox.org/permlink/JWeyXwrVRvsn9cBj
Carlo Gỗ

Có thể là C ++ 17, không phải C ++ 14 và thậm chí không phải C ++ 17 trong các phiên bản gcc 6.3.0 trở xuống trước đó, nó không phải là một thứ tiêu chuẩn. Nhưng cảm ơn vì đã đề cập đến điều này.
Wormer

À vâng, bạn nói đúng. Tôi đã không thử c ++ 14 trên đũa phép. Ồ, đó là phần mà tôi đã nói "Điều này về mặt lý thuyết vẫn yêu cầu bạn xác định hằng số". Vì vậy, bạn đúng rằng nó không phải là 'tiêu chuẩn'.
Carlo Wood,

3

C ++ cho phép các thành viên const tĩnh được định nghĩa bên trong một lớp

Không, 3.1 §2 nói:

Một khai báo là một định nghĩa trừ khi nó khai báo một hàm mà không chỉ định phần thân của hàm (8.4), nó chứa mã định nghĩa bên ngoài (7.1.1) hoặc đặc tả liên kết (7.5) và không phải là bộ khởi tạo cũng không phải là một hàm, nó khai báo một dữ liệu tĩnh thành viên trong định nghĩa lớp (9.4), nó là một khai báo tên lớp (9.1), nó là một khai báo không rõ ràng (7.2), hoặc nó là một khai báo typedef (7.1.3), một khai báo sử dụng (7.3. 3), hoặc chỉ thị sử dụng (7.3.4).

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.