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

Такое же имя класса в разных файлах С++

Если два файла С++ имеют разные определения классов с одинаковым именем, тогда, когда они компилируются и связаны, что-то выкидывается даже без предупреждения. Например,

// a.cc
class Student {
public:
    std::string foo() { return "A"; }
};
void foo_a()
{
    Student stu;
    std::cout << stu.foo() << std::endl;
}

// b.cc
class Student {
public:
    std::string foo() { return "B"; }
};
void foo_b()
{
    Student stu;
    std::cout << stu.foo() << std::endl;
}

Если скомпилировать и связать вместе с помощью g++, оба будут выдавать "A" (если a.cc предшествует b.cc в порядке командной строки).

Аналогичная тема здесь. Я вижу, что пространство имен решит эту проблему, но я не знаю, почему компоновщик даже не снимает предупреждение. И если одно определение класса имеет дополнительную функцию, которая не определена в другом, скажем, если b.cc обновляется как:

// b.cc
class Student {
public:
    std::string foo() { return "B"; }
    std::string bar() { return "K"; }
};
void foo_b()
{
    Student stu;
    std::cout << stu.foo() << stu.bar() << std::endl;
}

Тогда stu.bar() работает хорошо. Спасибо всем, кто может сказать мне, как компилятор и компоновщик работают в такой ситуации.

В качестве дополнительного вопроса, если классы определены в файлах заголовков, должны ли они всегда быть обернуты безымянным пространством имен, чтобы избежать такой ситуации? Есть ли побочные эффекты?

4b9b3361

Ответ 1

Это нарушение одного правила определения (С++ 03, 3.2/5 "Одно правило определения" ), в котором говорится (среди прочего):

Может быть более одного определения типа класса (раздел 9),... в программе, при условии, что каждое определение появляется в другом и при условии, что определения удовлетворяют следующему требования. Учитывая такую ​​сущность D, определенную более чем в одном единицы перевода, затем

  • каждое определение D должно состоять из одной и той же последовательности токенов;

Если вы нарушаете одно правило определения, поведение undefined (это означает, что могут произойти странные вещи).

Компонент видит несколько определений Student::foo() - один в объектном файле и один в b. Однако он не жалуется на это; он просто выбирает один из двух (как это бывает, первый из них встречается). Эта "мягкая" обработка повторяющихся функций, по-видимому, происходит только для встроенных функций. Для не-встроенных функций компоновщик будет жаловаться на несколько определений и откажется создавать исполняемый файл (могут быть варианты, которые ослабляют это ограничение). Подобным образом ведут себя как GNU ld, так и MSVC-компоновщик.

Поведение имеет смысл; встроенные функции должны быть доступны в каждой единицы перевода, в которой они используются. И в общем случае они должны иметь доступные не-встроенные версии (в случае, если вызов не вставлен или принят адрес функции). inline - это просто бесплатный проход вокруг правила единого определения, но для его работы все встроенные определения должны быть одинаковыми.

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

Что касается вашего второго вопроса:

В качестве дополнительного вопроса, если классы определены в файлах заголовков, следует они всегда должны быть обернуты безымянным пространством имен, чтобы избежать такой ситуации? Есть ли побочные эффекты?

Вы почти наверняка не хотите этого делать, каждый класс будет отдельным типом в каждой единицы перевода, поэтому технически экземпляры класса они не могут быть переданы из одной единицы перевода в другую (указателем, ссылкой или копированием). Кроме того, вы получите несколько экземпляров любых статических элементов. Возможно, это не сработает.

Поместите их в разные пространства имен с именами.

Ответ 2

Вы нарушили одно правило определения для определений классов, и язык специально запрещает это делать. Для компилятора/компоновщика не требуется предупреждать или диагностировать, и такой сценарий, безусловно, не гарантированно работает так, как ожидалось в этом случае.

Ответ 3

но я не знаю, почему компоновщик даже не снимает предупреждение.

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

Это легко, если классы имеют разные размеры, разные члены или разные базовые классы. Но как только все эти факторы равны, у вас все равно могут быть разные определения элементов, и компоновщик должен будет использовать некоторую расширенную интроспекцию для их сравнения. Даже если это всегда было возможно (и я не уверен, что это так, поскольку объектные файлы могут быть скомпилированы с различными параметрами, что приводит к разным выводам), это, безусловно, очень неэффективно.

Как следствие, линкер просто признает, что то, что вы бросаете на него, не нарушает ODR. Не идеальная ситуация, но хорошо.

Ответ 4

Я думаю, что ваш "лишний" вопрос является ключом к основному вопросу.

Если я понимаю ваш дополнительный вопрос, то я думаю, что вы не хотите обертывать их в пространстве имен, так как если вы # включаете один и тот же класс в несколько файлов .cc, то вы, вероятно, захотите использовать только одну копию каждого метода, даже если они определены внутри класса.

Этот (вид) объясняет, почему вы получаете только одну версию каждой функции в своем основном примере. Я ожидаю, что компоновщик просто предполагает, что две функции идентичны - генерируются из одного и того же #include источника. Было бы неплохо, если бы линкер обнаруживал, когда они были разными, и выдавал предупреждение, но я думаю, что это сложно.

Как указывает Mark B, практический ответ: "Просто не туда".

Ответ 5

Помимо нарушения одного правила определения, вы не видите жалобы компилятора из-за Обозначение имени в С++

Изменить: Как отметил Конрад Рудольф: "Переплетенные имена в этом случае будут такими же.