Как тип, который используется только в одном блоке компиляции, нарушает правило одного определения?
Мне сказали, что эти типы, которые видны там, есть уникальная единица перевода, были нарушены Правилом Единого Определения. Может кто-нибудь объяснить это?
//File1.cpp
#include "StdAfx.h"
static struct S { int Value() { return 1; } } s1;
int GetValue1() { return s1.Value(); }
//File2.cpp
#include "StdAfx.h"
static struct S { int Value() { return 2; } } s2;
int GetValue2() { return s2.Value(); }
// main.cpp
#include "stdafx.h"
extern int GetValue1();
extern int GetValue2();
int _tmain(int argc, _TCHAR* argv[])
{
if( GetValue1() != 1 ) throw "ODR violation";
if( GetValue2() != 2 ) throw "ODR violation";
return 0;
}
Я знаю, как исправить эту проблему. Согласно названию, я искал, почему это было нарушение ODR. Как это нарушает: "В любой единицы перевода шаблон, тип, функция или объект могут иметь не более одного определения".? Или, возможно, это нарушает другую часть правила.
Ответы
Ответ 1
Вы определили struct S
в глобальном пространстве имен двумя разными способами, что нарушает правило одного определения. В частности, существуют два разных определения ::S::Value()
, и это undefined, которое на самом деле будет вызвано.
Вы должны использовать безымянные пространства имен, чтобы убедиться, что определенная именованная версия struct S
определена в каждой единицы перевода:
namespace { struct S {int Value() {return 1;}} s1; }
int GetValue1() {return s1.Value();}
В Правиле Единого определения намного больше, чем в первом абзаце, который вы цитируете. В последнем абзаце в основном говорится, что некоторые вещи, включая определения классов, могут появляться более одного раза в программе, если они все одинаковы. Ваш код нарушает это последнее условие. Или в (сокращенных) словах стандарта:
В программе может быть несколько определений типа класса..., при условии, что каждое определение отображается в другой единицы перевода и при условии, что определения удовлетворяют следующим требованиям. Учитывая такую сущность, названную D, определенную более чем в одной единицы перевода, каждое определение D должно состоять из одной и той же последовательности токенов.
Ответ 2
Проблема заключается в том, что хотя s1
и s2
имеют только внутреннюю связь, оба соответствующих определения S
имеют внешнюю связь.
Что вы хотите сделать, это использовать анонимное пространство имен:
//File1.cpp
#include "StdAfx.h"
namespace {
struct S { int Value() { return 1; } } s1;
}
int GetValue1() { return s1.Value(); }
//File2.cpp
#include "StdAfx.h"
namespace {
struct S { int Value() { return 2; } } s2;
}
int GetValue2() { return s2.Value(); }
Edit:
Все внутри анонимного пространства имен, включая определения класса, имеет внутреннюю связь.
Определения в анонимном пространстве имен по-прежнему имеют внешнюю связь, но компилятор гарантирует, что они получат уникальные имена, которые не будут сталкиваться с любыми определениями из других единиц перевода.
Ответ 3
Это небезопасно, потому что у вас две структуры с именем S
. Ключевое слово static
применяется только к объявлению переменной; это эквивалентно тому, что вы написали:
struct S {
int Value() {return 1;}
};
static S s1;
Компилятор не замечает этого во время компиляции, потому что он имеет дело с каждой единицей перевода отдельно. Функции Value
в структурах искажаются точно с тем же именем и становятся слабыми глобальными символами в объектных файлах, поэтому компоновщик не выдает ошибку о столкновении имен символов; он просто выбирает один для использования в полностью связанном двоичном файле. Вероятно, это будет первое определение символа, что означает, что вы можете получить другое поведение в зависимости от порядка, с которым вы связывали объекты:
> g++ -o test test.o test1.o test2.o && ./test
s1 is 1
s2 is 1
> g++ -o test test.o test2.o test1.o && ./test
s1 is 2
s2 is 2
Вы можете обойти это путем либо обертывания структур в анонимных пространствах имен (что сделает локальные символы функций Value
локальными, а не слабыми глобальными):
namespace {
struct S {
int Value() {return 1;}
} s1;
}
Или просто удалить имя структуры, так как оно вам действительно не нужно:
struct {
int Value() {return 1;}
} s1;