Как может тип, который используется только в одном модуле компиляции, нарушать Правило Одного Определения? - PullRequest
12 голосов
/ 18 августа 2010

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

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

Ответы [ 3 ]

11 голосов
/ 18 августа 2010

Проблема в том, что, хотя 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:

Все внутри анонимного пространства имен, включая определения классов, имеет внутреннюю связь.

Определения в анонимном пространстве имен все еще имеют внешнюю связь, но компилятор гарантирует, что они получат уникальные имена, которые не будут конфликтовать с какими-либо определениями из других единиц перевода.

10 голосов
/ 18 августа 2010

Это небезопасно, потому что у вас есть две структуры с именем 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;
7 голосов
/ 18 августа 2010

Вы определили struct S в глобальном пространстве имен двумя различными способами, что нарушает правило единого определения.В частности, существует два различных определения ::S::Value(), и оно не определено, и в действительности оно будет вызываться.

Вы должны использовать безымянные пространства имен, чтобы убедиться, что в каждом из них определена версия с struct Sединица перевода:

namespace { struct S {int Value() {return 1;}} s1; }
int GetValue1() {return s1.Value();}

Правило единого определения намного больше, чем первый абзац, который вы цитируете.Последний абзац в основном говорит, что некоторые вещи, в том числе определения классов, могут появляться в программе более одного раза, , пока они все идентичны.Ваш код нарушает это последнее условие.Или, в (сокращенных) словах Стандарта:

В программе может быть несколько определений типа класса ... при условии, что каждое определение появляется в разных единицах перевода, ипри условии, что определения удовлетворяют следующим требованиям.Если такой объект с именем D определен более чем в одной единице перевода, то каждое определение D должно состоять из одной и той же последовательности токенов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...