2008-12-16 11 views
65

zauważyłem C++ nie zostanie skompilowany następujące:Dlaczego nie mogę mieć nieintegralnego elementu stałej stałej w klasie?

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

Jednak będzie ona szczęśliwie umożliwiają zmianę gdzie podwójna jest zmieniany na int, unsigned, czyli wszelkiego rodzaju integralną:

moje rozwiązanie było zmieniać je czytać:

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

i zrozumieć, że kompilator będzie wystarczająco silny, aby wbudować w razie potrzeby ... ale to mnie opuścił cur ious.

Dlaczego projektant (y) C++ pozwoliłby mi na statyczne const int lub unsigned, ale nie podwójne?

Edytuj: Używam Visual Studio 7.1 (.net 2003) w systemie Windows XP.

Edit2:

Pytanie zostało odebrane, ale na zakończenie, błąd widziałam:

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

jaki kompilator/platforma, czy widzisz go na wielokrotnościach? – warren

+0

Jaki komunikat błędu pojawia się w VS7.1? –

Odpowiedz

43

Problemem jest to, że z liczbą całkowitą, kompilator zwykle nie musi kiedykolwiek stworzyć adres pamięci dla stała. Nie istnieje w środowisku wykonawczym, a każde jego użycie zostaje wprowadzone do otaczającego kodu. Nadal może zdecydować o nadaniu mu lokalizacji pamięci - jeśli jego adres jest kiedykolwiek zajęty (lub jeśli jest przekazywany przez odwołanie do funkcji), musi to zrobić. Aby podać adres, należy go zdefiniować w jakiejś jednostce tłumaczeniowej. W takim przypadku należy oddzielić deklarację od definicji, ponieważ w przeciwnym razie zostałaby zdefiniowana w wielu jednostkach tłumaczeniowych.

Używając g ++ bez optymalizacji (-O0), automatycznie wstawia stałe zmienne całkowite, ale nie stałe wartości podwójne. Przy wyższych poziomach optymalizacji (na przykład -O1), powoduje on ciągłe podwojenie. Tak więc, następujący kod kompiluje w -O1 ale nie -O0: linia

// 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 

Dla maksymalnej przenośności, należy zadeklarować stałe w plikach nagłówkowych i zdefiniować je raz w jakimś pliku źródłowego . Bez optymalizacji nie zaszkodzi to wydajności, ponieważ i tak nie optymalizujesz, ale przy włączonych optymalizacjach może to zaszkodzić wydajności, ponieważ kompilator nie może już wstawiać tych stałych do innych plików źródłowych, chyba że włączysz "optymalizację całego programu" .

+8

static const double d = 1.0; nie jest poprawny C++. nie powinno się w ogóle kompilować. ta forma jest dozwolona tylko dla typów integralnych. z tego powodu min i max w numeric_limits są funkcjami, a nie statycznymi. nie jestem pewien, dlaczego to dla ciebie kompiluje. –

+3

wydaje się być rozszerzeniem gcc. kompilacja z -przykładowymi plonami: "foo.cpp: 4: error: ISO C++ zabrania inicjowania stałej stałej" d "niecałkowitego typu" const double "" –

+0

@ JohannesSchaub-litb: dziękuję za śledztwo. odpowiedź ta powinna być edytowana u góry, aby to zasygnalizować. –

3

Nie wiem, dlaczego to traktować podwójne różni się od wew. Myślałem, że wcześniej użyłem tego formularza. Oto alternatywna obejście:

class Now_Better 
{ 
    static double const d; 
}; 

A w pliku .cpp:

double const Now_Better::d = 1.0; 
+0

Tak, odłączyłem się od tego możliwego rozwiązania tylko dlatego, że myślałem, że moje było łatwiejsze do odczytania. Nie lubię * mieć *, aby zadeklarować i zainicjować wartość w oddzielnych plikach (Moja klasa jest w .h). Dziękuję Ci. –

15

nie widzę powodu, dlaczego technicznej

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

jest zabronione.Każda sytuacja, w której działa, jest spowodowana niemobilnymi funkcjami zdefiniowanymi przez implementację. Wydaje się, że są one ograniczone tylko do użytku. W przypadku stałych integralnych zainicjowanych w definicjach klas można ich użyć i przekazać do szablonów jako argumentów innych niż typy i użyć ich jako rozmiarów wymiarów tablicy. Ale nie można tego zrobić dla stałych zmiennoprzecinkowych. Dopuszczenie parametrów zmiennoprzecinkowych szablonów przyniesie własny zestaw reguł, które naprawdę nie będą warte problemów.

Niemniej wersja przyszłorocznego C++ pozwoli, że za pomocą constexpr:

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

i uczyni type::value stałej ekspresji. W międzyczasie, najlepiej jest podążać za wzór także używany przez std::numeric_limits:

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

To nie powróci stałą ekspresję (wartość nie jest znany w czasie kompilacji), ale to tylko znaczenie teoretyczne, ponieważ praktycznych wartość i tak zostanie wstawiona. Zobacz propozycję constexpr. Zawiera

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

Nie cicho rozumiem "Dopuszczenie parametrów zmiennoprzecinkowych szablonów przyniesie własny zestaw reguł, które naprawdę nie są warte problemów.". Również trudności w uzyskaniu "Jeśli ocena wyrazu jest oceniana w środowisku translacji, arytmetyczna precyzja i zakres powinny być co najmniej tak duże, jak gdyby wyrażenie było oceniane w środowisku wykonawczym." – Chubsdad

+2

@Chubsdad the first jest z powodu zwykłej niedokładności obliczeń zmiennoprzecinkowych, które mogą się różnić od implementacji do implementacji. Podczas gdy '1 + 1' jest zawsze' 2' na jakiejkolwiek implementacji, podobnie proste obliczenia mogą dać różne wyniki z matematyką zmiennoprzecinkową w różnych modelach zmiennoprzecinkowych (nieważne, nie lubię tych problemów, ale wiem, że one istnieją) . –

+0

W przypadku tego ostatniego problemu, tak naprawdę nie znam racji. Należy pamiętać, że środowisko kompilujące może się różnić od środowiska wykonawczego dla kompilatorów krzyżowych. Uzasadnieniem może być tutaj zapewnienie, że duża dokładność nie zostanie pogorszona przez obliczenie wyniku podczas kompilacji. Ale może możesz z tego zrobić oddzielne pytanie SO. –

7

to naprawdę nie dają przesłanek, ale oto co Stroustrup ma do powiedzenia na ten temat w "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.

A w dodatku C (technicznych) w sekcji C.5 (Constant Expressions), Stroustrup ma do powiedzenia o „stałych wyrażeń”:

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.

nocie, że dość dużo pomija zmiennoprzecinkowych, jak jest w stanie grać w ' stałe wyrażenia ". Podejrzewam, że zmiennoprzecinkowe zostało pominięte w tego typu wyrażeniach stałych tylko dlatego, że nie są one wystarczająco "proste".

+0

C++ 1x naprawi tę nieprawidłowość: (ostatnia wersja robocza): "Obiekt lub nieprzeciążona funkcja, której nazwa pojawia się jako potencjalnie oszacowane wyrażenie, jest używana, chyba że jest to obiekt, który spełnia wymagania dotyczące wyświetlania w wyrażeniu stałym". Statyka musi być zdefiniowana tylko jeśli jest używana, więc rozwiązuje to –

+0

, nie zawierając definicji w większości dzisiejszych implementacji z tego powodu: "Jeśli program zawiera naruszenie reguły, dla której nie jest wymagana diagnostyka, niniejszy Międzynarodowy Standard nie nakłada żadnych wymagań implementacje w odniesieniu do tego programu. " –

+0

Reguła stwierdzająca, że ​​musisz podać definicję, jest oznaczona "nie wymaga diagnostyki". Więc większość kompilacji (włączając w to "comeau") po prostu idzie dobrze, chyba że podasz adres obiektu, a obiekt nie zostanie zainicjalizowany w ramach definicji klasy, w którym to czasie wystąpi błąd linkera. –

-1

tutaj jest moje zrozumienie na podstawie oświadczenia o Stroustrup za definicją w klasie

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

więc w zasadzie, jest to niedozwolone, ponieważ C++ to nie pozwalają. Aby uprościć reguły linkera, C++ wymaga, aby każdy obiekt miał unikalną definicję.

Człon statyczny ma tylko jedno wystąpienie w zakresie klasy, a nie jak zwykłe zmienne statyczne używane w C, które mają tylko jedną instancję wewnątrz jednej jednostki tłumaczeniowej.

Jeśli element statyczny jest zdefiniowany w klasie, a definicja klasy zostanie włączona do wielu jednostek tłumaczeniowych, tak aby linker musiał wykonać więcej pracy, aby zdecydować, który element statyczny powinien być używany jako jedyny przez całe powiązane tłumaczenie jednostka.

Jednak w przypadku zwykłych zmiennych statycznych można ich używać tylko w jednej jednostce tłumaczeniowej, nawet w przypadku różnych zmiennych statycznych w różnych jednostkach tłumaczeniowych o tej samej nazwie, nie wpływają one na siebie nawzajem. Linker może wykonać proste prace, aby połączyć regularne zmienne statyczne w jednej jednostce tłumaczeniowej.

w celu zmniejszenia komplikacji i podania funkcji podstawowej, C++ udostępnia jedyną w klasie definicję stałej statycznej typu całkowego lub typu wyliczeniowego.

Powiązane problemy