Подтвердить что ты не робот

Как тип, который используется только в одном блоке компиляции, нарушает правило одного определения?

Мне сказали, что эти типы, которые видны там, есть уникальная единица перевода, были нарушены Правилом Единого Определения. Может кто-нибудь объяснить это?

//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. Как это нарушает: "В любой единицы перевода шаблон, тип, функция или объект могут иметь не более одного определения".? Или, возможно, это нарушает другую часть правила.

4b9b3361

Ответ 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;