Tham chiếu không xác định đến thành viên lớp tĩnh


201

Bất cứ ai có thể giải thích tại sao mã sau sẽ không biên dịch? Ít nhất là trên g ++ 4.2.4.

Và thú vị hơn, tại sao nó sẽ biên dịch khi tôi chọn MEMBER thành int?

#include <vector>

class Foo {  
public:  
    static const int MEMBER = 1;  
};

int main(){  
    vector<int> v;  
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

Tôi đã chỉnh sửa câu hỏi để thụt mã theo bốn khoảng trắng thay vì sử dụng <pre> <code> </ code> </ pre>. Điều này có nghĩa là dấu ngoặc nhọn không được hiểu là HTML.
Steve Jessop

stackoverflow.com/questions/16284629/ khăn Bạn có thể tham khảo câu hỏi này.
Tooba Iqbal

Câu trả lời:


199

Bạn cần thực sự định nghĩa thành viên tĩnh ở đâu đó (sau định nghĩa lớp). Thử cái này:

class Foo { /* ... */ };

const int Foo::MEMBER;

int main() { /* ... */ }

Điều đó sẽ thoát khỏi các tài liệu tham khảo không xác định.


3
Điểm hay, khởi tạo số nguyên const tĩnh tạo một hằng số nguyên có phạm vi mà bạn không thể lấy địa chỉ và vectơ lấy tham số tham chiếu.
Evan Teran

10
Câu trả lời này chỉ giải quyết phần đầu tiên của câu hỏi. Phần thứ hai thú vị hơn nhiều: Tại sao việc thêm một diễn viên NOP làm cho nó hoạt động mà không yêu cầu khai báo bên ngoài?
tộc

32
Tôi chỉ dành một chút thời gian để nhận ra rằng nếu định nghĩa lớp nằm trong tệp tiêu đề, thì việc phân bổ biến tĩnh phải nằm trong tệp thực hiện, chứ không phải tiêu đề.
shanet

1
@ Sơnet: Điểm rất tốt - Đáng lẽ tôi phải đề cập đến điều đó trong câu trả lời của mình!
Hội trường Drew

Nhưng nếu tôi khai báo nó là const, tôi không thể thay đổi giá trị của biến đó sao?
Namratha

78

Vấn đề xảy ra là do sự xung đột thú vị của các tính năng C ++ mới và những gì bạn đang cố gắng thực hiện. Trước tiên, hãy xem push_backchữ ký:

void push_back(const T&)

Đó là mong đợi một tham chiếu đến một đối tượng của loại T. Theo hệ thống khởi tạo cũ, một thành viên như vậy tồn tại. Ví dụ, đoạn mã sau sẽ biên dịch tốt:

#include <vector>

class Foo {
public:
    static const int MEMBER;
};

const int Foo::MEMBER = 1; 

int main(){
    std::vector<int> v;
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

Điều này là do có một đối tượng thực tế ở đâu đó có giá trị đó được lưu trữ trong đó. Tuy nhiên, nếu bạn chuyển sang phương pháp mới chỉ định các thành viên const tĩnh, như bạn có ở trên, Foo::MEMBERkhông còn là một đối tượng. Nó là một hằng số, hơi giống với:

#define MEMBER 1

Nhưng không có sự đau đầu của một macro tiền xử lý (và với loại an toàn). Điều đó có nghĩa là vectơ, đang mong đợi một tài liệu tham khảo, không thể có được một.


2
cảm ơn, điều đó đã giúp ... điều đó có thể đủ điều kiện cho stackoverflow.com/questions/1995113/strangest-lingu-feature nếu nó chưa có ở đó ...
Andre Holzner

1
Cũng đáng lưu ý rằng MSVC chấp nhận phiên bản không sử dụng mà không có khiếu nại.
porges

4
-1: Điều này đơn giản là không đúng. Bạn vẫn phải xác định các thành viên tĩnh được khởi tạo nội tuyến, khi chúng được sử dụng ở đâu đó. Tối ưu hóa trình biên dịch có thể thoát khỏi lỗi liên kết của bạn không thay đổi điều đó. Trong trường hợp này, chuyển đổi từ giá trị sang giá trị của bạn (nhờ (int)diễn viên) xảy ra trong đơn vị dịch với khả năng hiển thị hoàn hảo của hằng số và Foo::MEMBERkhông còn được sử dụng nữa. Điều này trái ngược với lời gọi hàm đầu tiên, trong đó một tham chiếu được truyền xung quanh và được đánh giá ở nơi khác.
Các cuộc đua nhẹ nhàng trong quỹ đạo

Thế còn void push_back( const T& value );? const&Có thể liên kết với giá trị.
Kostas

59

Tiêu chuẩn C ++ yêu cầu một định nghĩa cho thành viên const tĩnh của bạn nếu định nghĩa đó là cần thiết.

Định nghĩa là bắt buộc, ví dụ nếu địa chỉ của nó được sử dụng. push_backlấy tham số của nó bằng tham chiếu const, và vì vậy, trình biên dịch cần địa chỉ của thành viên của bạn và bạn cần xác định nó trong không gian tên.

Khi bạn sử dụng hằng số một cách rõ ràng, bạn đang tạo tạm thời và đây là tạm thời ràng buộc với tham chiếu (theo các quy tắc đặc biệt trong tiêu chuẩn).

Đây là một trường hợp thực sự thú vị và tôi thực sự nghĩ rằng đáng để nêu ra một vấn đề để std được thay đổi để có hành vi tương tự cho thành viên liên tục của bạn!

Mặc dù, theo một cách kỳ lạ, điều này có thể được coi là một cách sử dụng hợp pháp của toán tử '+' đơn nhất. Về cơ bản, kết quả của giá trị unary +là một giá trị và do đó, các quy tắc ràng buộc giá trị với tham chiếu const được áp dụng và chúng tôi không sử dụng địa chỉ của thành viên const tĩnh:

v.push_back( +Foo::MEMBER );

3
+1. Đúng, thật lạ khi đối với một đối tượng x loại T, biểu thức "(T) x" có thể được sử dụng để liên kết một const ref trong khi "x" đơn giản thì không thể. Tôi thích quan sát của bạn về "unary +"! Ai có thể nghĩ rằng "unary +" nhỏ đáng thương thực sự có một công dụng ... :)
j_random_hacker

3
Suy nghĩ về trường hợp chung ... Có bất kỳ loại đối tượng nào khác trong C ++ có thuộc tính mà nó (1) chỉ có thể được sử dụng như một giá trị nếu nó đã được xác định nhưng (2) có thể được chuyển đổi thành giá trị mà không bị định nghĩa?
j_random_hacker

Câu hỏi hay, và ít nhất tại thời điểm này tôi không thể nghĩ ra bất kỳ ví dụ nào khác. Điều này có lẽ chỉ ở đây vì ủy ban chủ yếu chỉ sử dụng lại cú pháp hiện có.
Richard Corden

@RichardCorden: làm thế nào để unary + giải quyết điều đó?
Blood-HaZaRd

1
@ Blood-HaZaRd: Trước khi tham chiếu giá trị, quá tải duy nhất push_backlà a const &. Sử dụng thành viên trực tiếp dẫn đến thành viên bị ràng buộc với tham chiếu, đòi hỏi nó phải có một địa chỉ. Tuy nhiên, việc thêm +tạo tạm thời với giá trị của thành viên. Tham chiếu sau đó liên kết với tạm thời đó thay vì yêu cầu thành viên có địa chỉ.
Richard Corden

10

Aaa.h

class Aaa {

protected:

    static Aaa *defaultAaa;

};

Aaa.cpp

// You must define an actual variable in your program for the static members of the classes

static Aaa *Aaa::defaultAaa;

1

Không biết tại sao dàn diễn viên hoạt động, nhưng Foo :: MEMBER không được phân bổ cho đến khi Foo được tải lần đầu tiên và vì bạn không bao giờ tải nó, nên nó không bao giờ được phân bổ. Nếu bạn có một tham chiếu đến một Foo ở đâu đó, nó có thể sẽ hoạt động.


Tôi nghĩ rằng bạn đang trả lời câu hỏi của riêng bạn: Dàn diễn viên hoạt động vì nó tạo ra một tài liệu tham khảo (tạm thời).
Jaap Versteegh

1

Với C ++ 11, ở trên có thể áp dụng cho các loại cơ bản như

class Foo {
public:  
  static constexpr int MEMBER = 1;  
};

Phần này constexprtạo ra một biểu thức tĩnh trái ngược với một biến tĩnh - và nó hoạt động giống như một định nghĩa phương thức nội tuyến cực kỳ đơn giản. Tuy nhiên, cách tiếp cận đã chứng minh một chút chao đảo với các constexprs chuỗi C bên trong các lớp mẫu.


Điều này hóa ra rất cần thiết đối với tôi, bởi vì "static const int MEMBER = 1;" là cần thiết để sử dụng MEMBER trong các thiết bị chuyển mạch, trong khi khai báo bên ngoài là cần thiết để sử dụng nó trong các vectơ và bạn không thể có cả hai cùng một lúc. Nhưng biểu thức bạn đưa ra ở đây không hoạt động cho cả hai, ít nhất là với trình biên dịch của tôi.
Nông dân Ben

0

Liên quan đến câu hỏi thứ hai: Push_Vf lấy tham chiếu làm tham số và bạn không thể có tham chiếu đến bộ nhớ const const tĩnh của một lớp / struct. Khi bạn gọi static_cast, một biến tạm thời được tạo. Và một tham chiếu đến đối tượng này có thể được thông qua, mọi thứ đều hoạt động tốt.

Hoặc ít nhất là đồng nghiệp của tôi, người đã giải quyết điều này đã nói như vậy.

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.