Tham chiếu không xác định đến const constpr tĩnh []


184

Tôi muốn có một static const charmảng trong lớp học của tôi. GCC phàn nàn và nói với tôi rằng tôi nên sử dụng constexpr, mặc dù bây giờ nó nói với tôi rằng đó là một tài liệu tham khảo không xác định. Nếu tôi làm cho mảng không phải là thành viên thì nó sẽ biên dịch. Chuyện gì đang xảy ra vậy?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}

1
Chỉ là một linh cảm, nó có hoạt động không nếu baz là int chẳng hạn? Bạn có thể truy cập nó? Nó cũng có thể là một lỗi.
FailsDev

1
@Pubby: Câu hỏi: Đơn vị dịch thuật nào sẽ được xác định trong? Trả lời: Tất cả mọi thứ bao gồm tiêu đề. Vấn đề: Vi phạm quy tắc một định nghĩa. Ngoại lệ: Các tích phân hằng số thời gian biên dịch có thể được "khởi tạo" trong các tiêu đề.
Vịt Mooing

Nó biên dịch tốt như một int@MooingDuck Nó hoạt động tốt như một người không phải là thành viên. Điều đó có vi phạm quy tắc không?
Pubby

@ Pubby8: ints gian lận. Là một người không phải là thành viên, điều đó không được phép, trừ khi các quy tắc thay đổi cho C ++ 11 (có thể)
Vịt Mooing

Xem xét các quan điểm và upvote, câu hỏi này yêu cầu một câu trả lời chi tiết hơn, mà tôi đã thêm vào dưới đây.
Shafik Yaghmour 4/03/2015

Câu trả lời:


187

Thêm vào tệp cpp của bạn:

constexpr char foo::baz[];

Lý do: Bạn phải cung cấp định nghĩa của thành viên tĩnh cũng như khai báo. Khai báo và trình khởi tạo đi vào bên trong định nghĩa lớp, nhưng định nghĩa thành viên phải tách biệt.


70
Điều đó có vẻ kỳ lạ ... vì dường như nó không cung cấp cho trình biên dịch một số thông tin mà trước đây nó không có ...
dây leo

32
Nó trông còn kỳ lạ hơn nữa khi bạn có khai báo lớp trong tệp .cpp! Bạn khởi tạo trường trong khai báo lớp, nhưng bạn vẫn cần " khai báo " trường bằng cách viết constexpr char foo :: baz [] bên dưới lớp. Có vẻ như các lập trình viên sử dụng constexpr có thể biên dịch chương trình của họ bằng cách làm theo một mẹo kỳ lạ: khai báo lại.
Lukasz Czerwinski

5
@LukaszCzerwinski: Từ bạn đang tìm kiếm là "định nghĩa".
Kerrek SB

5
Phải, không có thông tin mới: tuyên bố sử dụngdecltype(foo::baz) constexpr foo::baz;
không phải người dùng

6
biểu hiện sẽ như thế nào nếu foo được templatized? cảm ơn.
Hei

80

C ++ 17 giới thiệu các biến nội tuyến

C ++ 17 khắc phục vấn đề này cho constexpr staticcác biến thành viên yêu cầu định nghĩa ngoài dòng nếu nó được sử dụng odr. Xem nửa sau của câu trả lời này để biết chi tiết trước C ++ 17.

Đề xuất biến số nội tuyến P0386 giới thiệu khả năng áp dụng công inlinecụ xác định cho các biến. Đặc biệt trong trường hợp này constexprngụ ý inlinecho các biến thành viên tĩnh. Đề xuất nói:

Trình xác định nội tuyến có thể được áp dụng cho các biến cũng như các hàm. Một biến được khai báo nội tuyến có cùng ngữ nghĩa với một hàm được khai báo nội tuyến: nó có thể được định nghĩa, theo định nghĩa, trong nhiều đơn vị dịch, phải được định nghĩa trong mọi đơn vị dịch trong đó sử dụng odr và hành vi của chương trình như thể có chính xác một biến.

và sửa đổi [basic.def] p2:

Một tuyên bố là một định nghĩa trừ khi
...

  • nó khai báo một thành viên dữ liệu tĩnh bên ngoài một định nghĩa lớp và biến được định nghĩa trong lớp với bộ xác định constexpr (cách sử dụng này không được dùng nữa; xem [depr.static_constexpr]),

...

và thêm [depr.static_constexpr] :

Để tương thích với các tiêu chuẩn quốc tế C ++ trước đó, một thành viên dữ liệu tĩnh constexpr có thể được phân phối lại một cách dự phòng bên ngoài lớp mà không có trình khởi tạo. Việc sử dụng này không được chấp nhận. [ Thí dụ:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

 - ví dụ cuối]


C ++ 14 trở về trước

Trong C ++ 03, chúng tôi chỉ được phép cung cấp các trình khởi tạo trong lớp cho các kiểu tích phân const hoặc kiểu liệt kê const , trong C ++ 11 sử dụng constexprđiều này đã được mở rộng thành các kiểu chữ .

Trong C ++ 11, chúng tôi không cần cung cấp định nghĩa phạm vi không gian tên cho constexprthành viên tĩnh nếu nó không được sử dụng , chúng ta có thể thấy điều này từ phần dự thảo tiêu chuẩn C ++ 11 9.4.2 [class.static.data] nói ( nhấn mạnh của tôi đi về phía trước ):

[...] Một thành viên dữ liệu tĩnh có kiểu chữ có thể được khai báo trong định nghĩa lớp với trình xác định constexpr; nếu vậy, khai báo của nó sẽ chỉ định một bộ khởi tạo dấu ngoặc hoặc hoặc bằng nhau trong đó mọi mệnh đề khởi tạo là một biểu thức gán là một biểu thức không đổi. [Lưu ý: Trong cả hai trường hợp này, thành viên có thể xuất hiện trong các biểu thức không đổi. Lưu ý về mối quan hệ] Thành viên vẫn sẽ được xác định trong phạm vi không gian tên nếu nó được sử dụng odr (3.2) 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.

Vì vậy, câu hỏi trở thành, được baz sử dụng ở đây:

std::string str(baz); 

và câu trả lời là , và vì vậy chúng tôi cũng yêu cầu một định nghĩa phạm vi không gian tên.

Vậy làm thế nào để chúng ta xác định nếu một biến được sử dụng odr ? Từ ngữ C ++ 11 ban đầu trong phần 3.2 [basic.def.odr] nói:

Một biểu thức có khả năng được đánh giá trừ khi nó là toán hạng không được đánh giá (Điều 5) hoặc biểu thức con của nó. Một biến có tên xuất hiện dưới dạng biểu thức được đánh giá tiềm năng được sử dụng odr trừ khi nó là đối tượng thỏa mãn các yêu cầu xuất hiện trong biểu thức không đổi (5.19) và chuyển đổi lvalue sang rvalue (4.1) ngay lập tức được áp dụng .

Vì vậy, bazkhông mang lại một biểu thức không đổi nhưng chuyển đổi lvalue-rvalue không được áp dụng ngay lập tức vì nó không được áp dụng do bazlà một mảng. Điều này được đề cập trong phần 4.1 [conv.lval] có nội dung:

Một giá trị (3.10) của loại T không có chức năng, không có mảng có thể được chuyển đổi thành giá trị.53 [...]

Những gì được áp dụng trong chuyển đổi mảng thành con trỏ .

Từ ngữ này của [basic.def.odr] đã bị thay đổi do Báo cáo khuyết tật 712 do một số trường hợp không được bao phủ bởi từ ngữ này nhưng những thay đổi này không thay đổi kết quả cho trường hợp này.


Vì vậy, chúng ta rõ ràng rằng constexprhoàn toàn không có gì để làm với nó? ( bazdù sao cũng là một biểu thức không đổi)
MM

@MattMcNabb constexpr well là bắt buộc nếu thành viên không phải là một integral or enumeration typenhưng nếu không, vâng, điều quan trọng là nó là một biểu thức không đổi .
Shafik Yaghmour

Trong đoạn đầu tiên, "ord-used" nên đọc là "odr-used", tôi tin, nhưng tôi không bao giờ chắc chắn với C ++
Egor Pasko

37

Đây thực sự là một lỗ hổng trong C ++ 11 - như những người khác đã giải thích, trong C ++ 11, một biến thành viên constexpr tĩnh, không giống như mọi loại biến toàn cầu constexpr khác, có liên kết bên ngoài, do đó phải được xác định rõ ràng ở đâu đó.

Điều đáng chú ý là trong thực tế, bạn có thể thường xuyên thoát khỏi các biến thành viên constexpr tĩnh mà không cần định nghĩa khi biên dịch với tối ưu hóa, vì chúng có thể được kết thúc trong tất cả các sử dụng, nhưng nếu bạn biên dịch mà không tối ưu hóa thường thì chương trình của bạn sẽ không liên kết được. Điều này làm cho đây là một cái bẫy ẩn rất phổ biến - chương trình của bạn biên dịch tốt với tối ưu hóa, nhưng ngay khi bạn tắt tối ưu hóa (có lẽ để gỡ lỗi), nó không thể liên kết.

Mặc dù vậy, tin tốt - lỗ hổng này đã được sửa trong C ++ 17! Cách tiếp cận hơi phức tạp mặc dù: trong C ++ 17, các biến thành viên constexpr tĩnh được đặt ngầm định . Có nội tuyến áp dụng cho các biến là một khái niệm mới trong C ++ 17, nhưng nó có hiệu quả phương tiện mà họ không cần một bất cứ nơi nào định nghĩa rõ ràng.


4
Lên thông tin C ++ 17. Bạn có thể thêm thông tin này vào câu trả lời được chấp nhận!
SR

5

Không phải là giải pháp thanh lịch hơn đang thay đổi char[]thành:

static constexpr char * baz = "quz";

Bằng cách này, chúng ta có thể có định nghĩa / khai báo / khởi tạo trong 1 dòng mã.


9
với char[]bạn có thể sử dụng sizeofđể lấy độ dài của chuỗi tại thời gian biên dịch, với char *bạn không thể (nó sẽ trả về chiều rộng của loại con trỏ, 1 trong trường hợp này).
gnzlbg

2
Điều này cũng tạo cảnh báo nếu bạn muốn nghiêm ngặt với ISO C ++ 11.
Shital Shah

Xem câu trả lời của tôi không thể hiện sizeofvấn đề này và có thể được sử dụng trong các giải pháp "chỉ tiêu đề"
Josh Greifer

4

Giải pháp của tôi cho liên kết bên ngoài của các thành viên tĩnh là sử dụng các constexprthành viên tham chiếu (điều này không gặp phải vấn đề @gnzlbg nêu ra như một nhận xét cho câu trả lời từ @deddebme).
Thành ngữ này rất quan trọng đối với tôi vì tôi không thích có nhiều tệp .cpp trong các dự án của mình và cố gắng giới hạn số lượng thành một, bao gồm không có gì ngoài #includes và một main()hàm.

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'

-1

Trên môi trường của tôi, gcc vesion là 5.4.0. Thêm "-O2" có thể khắc phục lỗi biên dịch này. Có vẻ như gcc có thể xử lý trường hợp này khi yêu cầu tối ưu hóa.

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.