2008-12-16 35 views
65

tôi nhận thấy C++ sẽ không biên dịch như sau:Tại sao tôi không thể có một thành viên const tĩnh không tách rời trong một lớp học?

class No_Good { 
    static double const d = 1.0; 
}; 

Tuy nhiên nó sẽ vui vẻ cho phép một sự thay đổi nơi đôi được thay đổi thành một int, unsigned, hoặc bất kỳ loại không thể thiếu:

Giải pháp của tôi đã thay đổi nó để đọc:

class Now_Good { 
    static double d() { return 1.0; } 
}; 

và con số trình biên dịch sẽ đủ thông minh để nội tuyến khi cần ... nhưng nó để lại cho tôi ious.

Tại sao nhà thiết kế C++ (s) cho phép tôi tĩnh const một int hoặc unsigned, nhưng không phải là gấp đôi?

Chỉnh sửa: Tôi đang sử dụng studio trực quan 7.1 (.net 2003) trên Windows XP.

Edit2:

Câu hỏi đã được trả lời, nhưng để hoàn thành, các lỗi tôi đã nhìn thấy:

error C2864: 'd' : only const static integral data members can be initialized inside a class or struct 
+0

trình biên dịch/nền tảng nào hoặc bạn thấy nó trên bội số? – warren

+0

Bạn nhận được thông báo lỗi nào trong VS7.1? –

Trả lời

43

Vấn đề là với một số nguyên, trình biên dịch thường không phải bao giờ tạo ra một địa chỉ bộ nhớ cho hằng số. Nó không tồn tại trong thời gian chạy, và mỗi lần sử dụng nó được đưa vào mã xung quanh. Nó vẫn có thể quyết định để cho nó một vị trí bộ nhớ - nếu địa chỉ của nó là bao giờ thực hiện (hoặc nếu nó được thông qua bởi const tham chiếu đến một chức năng), rằng nó phải. Để cung cấp cho nó một địa chỉ, nó cần phải được xác định trong một số đơn vị dịch thuật. Và trong trường hợp đó, bạn cần phân tách khai báo khỏi định nghĩa, vì nếu không nó sẽ được định nghĩa trong nhiều đơn vị dịch thuật.

Sử dụng g ++ không tối ưu hóa (-O0), nó tự động inline biến số nguyên không đổi nhưng không phải giá trị kép không đổi. Ở các mức tối ưu hóa cao hơn (ví dụ: -O1), nó tăng gấp đôi liên tục. Do đó, đoạn mã sau biên dịch tại -O1 nhưng KHÔNG tại -O0: dòng

// File a.h 
class X 
{ 
public: 
    static const double d = 1.0; 
}; 

void foo(void); 

// File a.cc 
#include <stdio.h> 

#include "a.h" 

int main(void) 
{ 
    foo(); 
    printf("%g\n", X::d); 

    return 0; 
} 

// File b.cc 
#include <stdio.h> 

#include "a.h" 

void foo(void) 
{ 
    printf("foo: %g\n", X::d); 
} 

Command:

g++ a.cc b.cc -O0 -o a # Linker error: ld: undefined symbols: X::d 
g++ a.cc b.cc -O1 -o a # Succeeds 

Đối với tính di động tối đa, bạn nên khai báo các hằng của bạn trong các tập tin tiêu đề và định nghĩa chúng một lần trong một số tập tin nguồn . Không tối ưu hóa, điều này sẽ không ảnh hưởng đến hiệu suất, vì bạn không tối ưu hóa hiệu năng, nhưng với việc tối ưu hóa được kích hoạt, điều này có thể làm giảm hiệu suất, vì trình biên dịch không còn có thể đưa các hằng số đó vào các tệp nguồn khác, trừ khi bạn bật "tối ưu hóa toàn bộ chương trình" .

+8

static const double d = 1.0; không hợp lệ C++. nó không nên biên dịch chút nào. biểu mẫu đó chỉ được phép cho các loại tích phân. đó là lý do min và max trong số_lập số là các hàm và không phải hằng số tĩnh. Tôi không chắc tại sao nó biên dịch cho bạn. –

+3

nó có vẻ là một phần mở rộng gcc. biên dịch với sản lượng khổng lồ: "foo.cpp: 4: error: ISO C++ cấm khởi tạo hằng số thành viên 'd' của loại không tách rời 'const đôi'" –

+0

@ JohannesSchaub-litb: cảm ơn bạn đã điều tra. câu trả lời này nên được chỉnh sửa ở trên cùng để báo hiệu điều đó. –

3

Tôi không biết tại sao nó sẽ đối xử với một đôi khác nhau từ một int. Tôi nghĩ rằng tôi đã sử dụng hình thức đó trước đây. Dưới đây là một phương pháp thay thế:

class Now_Better 
{ 
    static double const d; 
}; 

Và trong tập tin cpp của bạn:

double const Now_Better::d = 1.0; 
+0

Vâng, tôi đã tránh xa giải pháp có thể này chỉ vì tôi nghĩ rằng tôi dễ đọc hơn một chút. Tôi không thích * có * để khai báo và khởi tạo một giá trị trong các tệp riêng biệt (lớp của tôi ở trong .h). Cảm ơn bạn. –

15

tôi thấy không có lý do tại sao kỹ thuật

struct type { 
    static const double value = 3.14; 
}; 

bị cấm.Bất kỳ dịp nào bạn tìm thấy nơi nó hoạt động là do tính năng được xác định không thực hiện di động. Họ cũng dường như chỉ sử dụng hạn chế. Đối với các hằng số tách rời được khởi tạo trong các định nghĩa lớp, bạn có thể sử dụng chúng và chuyển chúng vào các khuôn mẫu như các đối số không phải kiểu, và sử dụng chúng như là kích thước của các tham số mảng. Nhưng bạn không thể làm như vậy cho các hằng số dấu chấm động. Cho phép các tham số mẫu dấu phẩy động sẽ mang lại bộ quy tắc riêng của nó không thực sự đáng giá.

Tuy nhiên, C++ tiếp theo phiên bản sẽ cho phép điều đó bằng constexpr:

struct type { 
    static constexpr double value = 3.14; 
    static constexpr double value_as_function() { return 3.14; } 
}; 

Và sẽ làm cho type::value một biểu thức hằng. Trong khi đó, đặt cược tốt nhất của bạn là làm theo mô hình cũng được sử dụng bởi std::numeric_limits:

struct type { 
    static double value() { return 3.14; } 
}; 

Nó sẽ không trả về một biểu thức hằng số (giá trị không được biết đến tại thời gian biên dịch), nhưng điều đó chỉ những vấn đề lý thuyết, vì thực tế các giá trị sẽ được inlined anyway. Xem đề xuất constexpr. Nó chứa

4.4

Floating-point constant expressions

Traditionally, evaluation of floating-point constant expression at compile-time is a thorny issue. For uniformity and generality, we suggest to allow constant-expression data of floating point types, initialized with any floating-point constant expressions. That will also increase compatibility with C99 [ISO99, §6.6] which allows

[#5] An expression that evaluates to a constant is required in several contexts. If a floating expression is evaluated in the translation envi- ronment, the arithmetic precision and range shall be at least as great as if the expression were being evaluated in the execution environ- ment.

+0

Tôi không im lặng hiểu "Cho phép các tham số mẫu dấu phẩy động sẽ mang lại bộ quy tắc riêng của nó không thực sự đáng giá.". Cũng gặp khó khăn trong việc nhận được "Nếu một biểu thức fl oating được đánh giá trong môi trường dịch thuật, độ chính xác và phạm vi số học phải ít nhất là tuyệt vời như khi biểu thức được đánh giá trong môi trường thực thi." – Chubsdad

+2

@Chubsdad trước đây là do tính không chính xác thông thường của các tính toán điểm nổi, có thể khác với triển khai thực hiện. Trong khi '1 + 1' luôn là' 2' khi thực hiện, các phép tính đơn giản tương tự có thể mang lại các kết quả khác nhau với phép toán dấu chấm động trên các mô hình điểm nổi khác nhau (nhớ tôi, tôi không thích những vấn đề này nhưng tôi biết chúng tồn tại) . –

+0

Đối với vấn đề thứ hai, tôi không thực sự biết lý do. Xin lưu ý rằng môi trường biên dịch có thể khác với môi trường thực thi cho các trình biên dịch chéo. Lý do ở đây có thể để đảm bảo rằng một độ chính xác tuyệt vời sẽ không được thực hiện tồi tệ hơn bằng cách tính toán kết quả tại thời gian biên dịch. Nhưng có lẽ bạn có thể tạo một câu hỏi SO riêng biệt trong số này. –

7

Nó không thực sự đưa ra một lý do, nhưng đây là những gì Stroustrup đã nói về điều này trong "The C++ Programming Language Third Edition":

10.4.6.2 Member Constants

It is also possible to initialize a static integral constant member by adding a constant-expression initializer to its member declaration. For example:

class Curious { 
    static const int c1 = 7;  // ok, but remember definition 
    static int c2 = 11;    // error: not const 
    const int c3 = 13;    // error: not static 
    static const int c4 = f(17); // error: in-class initializer not constant 
    static const float c5 = 7.0; // error: in-class not integral 
    // ... 
}; 

However, an initialized member must still be (uniquely) defined somewhere, and the initializer may not be repeated:

const int Curious::c1; // necessary, but don't repeat initializer here 

I consider this a misfeature. When you need a symbolic constant within a class declaration, use an enumerator (4.8, 14.4.6, 15.3). For example:

class X { 
    enum { c1 = 7, c2 = 11, c3 = 13, c4 = 17 }; 
    // ... 
}; 

In that way, no member definition is needed elsewhere, and you are not tempted to declare variables, floating-point numbers, etc.

Và tại Phụ lục C (Kỷ nghệ) trong Phần C.5 (Biểu thức Không đổi), Stroustrup có nội dung này để nói về "các cụm từ không đổi":

In places such as array bounds (5.2), case labels (6.3.2), and initializers for enumerators (4.8), C++ requires a constant expression. A constant expression evaluates to an integral or enumeration constant. Such an expression is composed of literals (4.3.1, 4.4.1, 4.5.1), enumerators (4.8), and consts initialized by constant expressions. In a template, an integer template parameter can also be used (C.13.3). Floating literals (4.5.1) can be used only if explicitly converted to an integral type. Functions, class objects, pointers, and references can be used as operands to the sizeof operator (6.2) only.

Intuitively, constant expressions are simple expressions that can be evaluated by the compiler before the program is linked (9.1) and starts to run.

Lưu ý rằng anh ta bỏ đi nhiều điểm nổi khi có thể chơi trong ' biểu thức hằng số '. Tôi nghi ngờ rằng dấu chấm động bị loại ra khỏi các loại biểu thức không đổi này đơn giản chỉ vì chúng không đủ đơn giản.

+0

C++ 1x sẽ sửa lỗi đó: (đối tượng gần đây): "Một đối tượng hoặc hàm không quá tải có tên xuất hiện dưới dạng biểu thức đánh giá tiềm năng được sử dụng trừ khi nó là đối tượng đáp ứng các yêu cầu xuất hiện trong biểu thức không đổi". chỉ cần xác định số liệu thống kê nếu được sử dụng, vì vậy việc này giải quyết nó –

+0

không cung cấp định nghĩa nào trong hầu hết các ngày thực hiện 'vì một chương trình có vi phạm quy tắc không cần chẩn đoán, tiêu chuẩn này không yêu cầu triển khai liên quan đến chương trình đó. " –

+0

quy tắc cho biết bạn cần cung cấp định nghĩa được đánh dấu là "không cần chẩn đoán". Vì vậy, hầu hết các biên dịch (bao gồm cả comeau) chỉ cần đi OK trừ khi bạn lấy địa chỉ của đối tượng và đối tượng không được khởi tạo trong định nghĩa lớp, tại thời điểm đó một lỗi liên kết sẽ dẫn đến kết quả. –

-1

ở đây là sự hiểu biết của tôi dựa trên tuyên bố Stroustrup về định nghĩa trong lớp

A class is typically declared in a header file and a header file is typically included into many translation units. However, to avoid complicated linker rules, C++ requires that every object has a unique definition. That rule would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects.

http://www.stroustrup.com/bs_faq2.html#in-class

nên về cơ bản, điều này là không được phép vì C++ không cho phép điều này. Để làm cho các quy tắc liên kết trở nên đơn giản hơn, C++ yêu cầu mọi đối tượng đều có một định nghĩa duy nhất.

thành viên tĩnh chỉ có một trường hợp trong phạm vi lớp, không giống như biến tĩnh thông thường được sử dụng nhiều trong C, chỉ có một instatnce bên trong một đơn vị dịch.

Nếu thành viên tĩnh được xác định trong lớp và định nghĩa lớp sẽ được đưa vào nhiều đơn vị dịch, để người liên kết phải làm nhiều việc hơn để quyết định thành viên tĩnh nào nên được sử dụng làm người duy nhất thông qua tất cả các bản dịch liên quan đơn vị. Tuy nhiên, đối với các biến tĩnh thông thường, chúng chỉ có thể được sử dụng bên trong một đơn vị dịch, ngay cả trong trường hợp các biến tĩnh khác nhau trong đơn vị dịch khác nhau có cùng tên, chúng sẽ không ảnh hưởng lẫn nhau. Linker có thể làm công việc đơn giản để liên kết các biến tĩnh thông thường bên trong một đơn vị dịch.

để giảm các biến chứng và cung cấp chức năng cơ sở, C++ cung cấp định nghĩa duy nhất trong lớp cho một const tĩnh của loại tích phân hoặc liệt kê.

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