2016-11-17 11 views
7

Zostałem ukąszony przez nieprzyjemne naruszenie zasady "jednej definicji". Obecnie obawiam się, że w moich projektach mam wiele subtelnych błędów.Jedno ostrzeżenie o definicji definicji

Na przykład, poniższy program spowoduje null pointer dereference z Visual Studio 2015:

Source1.cpp: 
---------- 
struct S { 
    double d = 0; 
}; 

void Foo() { 
    S s; 
} 


Source2.cpp: 
----------- 
struct S { 
    int a = 0; 
}; 

int main() { 

    int value = 5; 
    int& valueRef = value; 
    S s;   // valueRef is erased due to S::d initialization from Source1.cpp 

    valueRef++; // crash 
} 

To kompiluje bez ostrzeżenia.

Jest nieprzyjemny, ponieważ Source2.cpp nie używa niczego od Source1.cpp. Jeśli usunę z projektu Source1.cpp, nadal się kompiluje i nie ma już problemu.

W dużych projektach wydaje się bardzo trudne zapewnienie, że żaden plik cpp "lokalnie" nie definiuje struktury lub klasy o już zdefiniowanej nazwie.

Mam kilka klas, takich jak Point, Serie, State, Item ... I chociaż to było OK w małych plików CPP, ale zdaję sobie sprawę, że nie jest bezpieczne.

Czy istnieje ostrzeżenie o kompilatorach do wychwytywania takich błędów? Jeśli nie, jakie są najlepsze praktyki, aby uniknąć naruszenia ODR?

+4

Definiowanie ich w anonimowym obszarze nazw. – molbdnilo

Odpowiedz

3

W tym konkretnym przypadku, na samym dole naruszenie ODR (który faktycznie prowadzi do problemu jesteś obserwacji) jest niejawnie zdefiniowane inline konstruktor klasy S. Twój program ma dwie niekompatybilne wersje wbudowanej funkcji S::S(), która może być postrzegana jako kolejne naruszenie ODR wywołane przez pierwotne naruszenie ODR (to jest ta sama klasa zdefiniowana inaczej).

Implementacja będzie trudna do "zobaczenia" tego błędu w bieżącym podejściu do infrastruktury kompilacji C++. Oczywiście można to zrobić przy wystarczającym wysiłku.

W tym przypadku, aby błąd był "widoczny", można jawnie zadeklarować i zdefiniować konstruktor klasy jako nieinstanowioną funkcję z pustym ciałem. Obecność dwóch nieliniowych S::S() spowoduje błąd łącznika.

Zrozumiałe, może się wydawać, że jest to zbyt sztuczny środek, nie do przyjęcia w niektórych przypadkach, ponieważ może zmienić status "zbiorczej" klasy.

4

Jeśli nie, jakie są najlepsze praktyki, aby uniknąć naruszenia ODR?

Zasadniczo mamy tutaj przestrzenie nazw.

Zastosowanie jeden znany nazw na komponencie (np boost, std, asio, sql, mytool, yourlib itp).

nazw nazwa jest faktycznie stanowi część nazwy, więc następujące:

namespace X { 
    struct S {}; 
} 

namespace Y { 
    struct S {}; 
} 

struct S {}; 

wyniki w trzech różnych klasach są zdefiniowane. Jeden nazywa się X::S, jeden nazywa się Y::S, a drugi to S, znany również jako ::S.

:: to globalna przestrzeń nazw . Unikanie deklarowania nazw jest dobrym pomysłem, ponieważ wszelkie komponenty języka C używane w twoim programie (lub naiwnie napisane składniki C++) szybko zanieczyszczają ten obszar nazw własnymi nazwami.

3
  • nie jest trudno zorganizować nazwy klasa/struct, nawet z milionami kodów, z mocnym namespace. Nie zapominaj, że namespace mogą definiować zagnieżdżone poziomy

  • jeśli naprawdę chcesz jakąś definicję „lokalnie”, spróbuj anonimowy namespace

  • Pamiętam, że średnia wyraźnie nie wymaga diagnostyki jeśli programista jest niezgodny ODR, więc liczyć na siebie.

Powiązane problemy