Dziwne, nieokreślone symbole stałych statycznych wewnątrz struktury / klasy

Albo jestem bardzo zmęczony, albo dzieje się coś dziwnego, o czym nie jestem świadom, ponieważ poniższy kod to , co skutkuje niezdefiniowanymi symbolami Foo::A i Foo::B podczas łączenia. Jest to zminimalizowane na tyle, na ile mogłem z większego projektu, ale pokazuje istotę tego, na co patrzę.

#include <algorithm>

struct Foo
{
    static const int A = 1;
    static const int B = 2;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}

Bez szablonu funkcji std:: min działa dobrze, tzn. po prostu zwraca Foo:: A. dobrze jest również przy definiowaniu statycznych wejść poza klasą / strukturą (global in ten prosty przypadek). Jednak gdy tylko znajdą się w środku, linker nie może ich znaleźć.

Czy ktoś może wyjaśnić, co się dzieje?
Author: Suma, 2011-02-03

6 answers

Potrzebna definicja

Podany kod jest niestandardowy. Chociaż możesz zapewnić inicjalizatory dla statycznych członków int const bezpośrednio w klasie, nadal musisz podać oddzielne definicje. Jest to dziwne, trochę nieoczekiwane, ale oczekuje się, że napiszesz to tak:

#include <algorithm>

struct Foo
{
    static const int A = 1;
    static const int B = 2;
};

const int Foo::A;
const int Foo::B;

int main()
{
    return std::min(Foo::A, Foo::B);
}

Cytat ze standardu można znaleźć w podobnym Pytaniu na const i statycznych specyfikacjach w c++

Dlaczego czasami kod "działa" bez definicji?

Jak dla dlaczego często możesz obejść się nawet bez podania definicji: jeśli używasz tych członków tylko w wyrażeniach stałych, kompilator zawsze rozwiąże je bezpośrednio i nie będzie już dostępu do rozdzielczości linkera. Jest to tylko wtedy, gdy użyjesz go w sposób, który nie może być obsługiwany bezpośrednio przez kompilator i tylko w takim przypadku linker wykryje, że symbol jest niezdefiniowany. Myślę, że jest to prawdopodobnie błąd w kompilatorze Visual Studio, ale biorąc pod uwagę naturę błędu wątpię, że będzie zawsze naprawiane.

Dlaczego twoje źródło zalicza się do kategorii "linker" to coś, czego nie widzę, trzeba by przeanalizować std:: min aby to zrozumieć. Uwaga: kiedy próbowałem go online z GCC, zadziałało, błąd nie został wykryty.

Alternatywa: użyj enum

Inną alternatywą jest użycie enum. Ta wersja może się również przydać, gdy trafisz na stary kompilator, który nie obsługuje statycznych inicjalizatorów const int "inline" (takich jak Visual Studio 6). Zauważ jednak, że z std:: min trafiasz w inne problemy z enumami i musisz użyć jawnej instancji lub castingu, lub mieć zarówno A jak i B W Jednym o nazwie enum jak w odpowiedź Nawaza :

struct Foo
{
    enum {A = 1};
    enum {B = 2};
};

int main()
{
    return std::min<int>(Foo::A, Foo::B);
}

Standardy

Uwaga: nawet Stroustrup C++ FAQ źle to rozumie i nie wymaga definicji tak ściśle jak standard:

Możesz przyjąć adres statycznego członka, jeśli (i tylko wtedy, gdy) ma on poza klasą definicja

Definicja jest wymagana przez standard w 9.4.2:

C++03]}

Element musi być nadal zdefiniowany w przestrzeni nazw, jeśli jest używany w programie, a definicja przestrzeni nazw nie powinna zawierać inicjalizatora

C++11 z wersji 9.4.2 jest nieco inny:

3 element jest nadal zdefiniowany w przestrzeni nazw, jeśli jest używany odr (3.2) w program

3.2 mówi o ODR-use:

3 zmienna x, której nazwa pojawia się jako potencjalnie oceniane wyrażenie ex, jest używana ODR, chyba że x jest obiektem spełniającym wymagania dotyczące pojawienia się w wyrażeniu stałym (5.19), a ex jest elementem zbioru potencjalnych wyników wyrażenia e, gdzie albo do e zastosowana jest konwersja lvalue-to-rvalue (4.1), albo E jest wyrażeniem odrzuconej wartości (Klauzula 5).

4 Każdy program powinien zawierać dokładnie jedną definicję każdej funkcji lub zmiennej nie-liniowej, która jest odr-używana w tym programie; nie jest wymagana diagnostyka.

Muszę przyznać, że nie jestem pewien, jakie są dokładne implikacje sformułowań C++11, ponieważ nie rozumiem zasad odr-use.

 42
Author: Suma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:24:25

Jeśli chcesz tylko wartości całkowe, możesz również zdefiniować enum:

#include <algorithm>

struct Foo
{
    enum integrals { A = 1, B = 2} ;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}
To więcej niż wystarczy. Nie potrzeba deklaracji poza klasą!

Demo Online: http://www.ideone.com/oE9b5

 3
Author: Nawaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-03 20:14:39

Musisz zdefiniować stałe statyczne poza definicją klasy.

struct Foo {
    static const int A;
    static const int B;
};

const int Foo::A = 1;
const int Foo::B = 2;
 2
Author: wilhelmtell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-03 20:06:34

Są tu dobre odpowiedzi, ale jedną dodatkową rzeczą jest to, że parametry std::min() są referencjami, co wymaga adresów przekazywanych zmiennych, a ponieważ te zmienne nie trafiają do pliku obiektowego dla jednostki kompilacji, linker nie może rozwiązać ich adresów.

Prawdopodobnie dostajesz to w nie zoptymalizowanej kompilacji, prawda?

Założę się, że nie dostaniesz tego z gcc, jeśli włączysz optymalizacje. Wywołanie do std::min() otrzyma inlined i odniesienia znikną.

Ponadto, jeśli przypisałbyś Foo::A i Foo::B do dwóch zmiennych lokalnych tuż przed wywołaniem std::min(), ten problem również zniknie.

Nie jest to idealne rozwiązanie, ale jeśli nie posiadasz kodu, który definiuje zmienne, które powodują ten problem, to jest to coś, co możesz wziąć pod uwagę.

 2
Author: stingoops,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-11 01:11:53

Skoro używasz struct jako przestrzeni nazw, dlaczego nie użyć przestrzeni nazw:

#include <algorithm>

namespace Foo
{
    const int A = 1;
    const int B = 2;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}
 1
Author: John Ripley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-04 02:18:58

Innym rozwiązaniem byłoby inline Twoje zmienne static, takie jak to będzie dostępne w ostatecznej jednostce tłumaczenia, która usuwa błąd niezdefiniowanych symboli.

Uwaga To będzie działać tylko w post C++11 AFAIK.

 0
Author: Moshe Rabaev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-13 18:13:09