C ++ компоновщик - отсутствие дублирующихся символов - PullRequest
5 голосов
/ 16 ноября 2009

Почему следующий код не дает мне ошибку компоновщика дубликатов символов для Impl?

Я столкнулся с этой проблемой в некотором унаследованном коде, и для простоты я воссоздаю более короткую версию.

У меня есть два класса, Foo и Bar, каждый из которых определяет разные версии одной и той же структуры (Impl) в каждом из своих файлов .cpp. Таким образом, каждый из Foo.cpp и Bar.cpp имеет одноименное определение Impl, но у каждого своя реализация встроенного конструктора.

И Foo, и Bar имеют переменную-член типа Impl, и каждый форвард объявляет Impl в своем файле .h.

Foo.cpp сообщает экземпляр Bar внутри своего конструктора. Что интересно, то, что создается, зависит от порядка связывания файлов.

Итак, эта команда компиляции:

g++ -o a.out main.cpp Bar.cpp Foo.cpp

приводит к выводу:

==> main()
Bar.cpp's Impl::Impl()
Bar.cpp's Impl::Impl()
<== main()

И эта команда:

g++ -o a.out main.cpp Foo.cpp Bar.cpp

приводит к выводу:

==> main()
Foo.cpp's Impl::Impl()
Foo.cpp's Impl::Impl()
<== main()

Я пробовал это с gcc 4.1.2, Visual Studio 2008 и Green Hills Multi 4.2.4, и все они дают одинаковый результат.


foo.h

#ifndef FOO_H

struct Impl;
class Bar;

class Foo
{
public:
   Foo();
   ~Foo();

private:
   Impl* p;
   Bar* bar;
};

#endif

foo.cpp

#include <iostream>
#include "Foo.h"
#include "Bar.h"

struct Impl
{
   Impl()
   {
      std::cout << "Foo.cpp's Impl::Impl()" << std::endl;
   }
};

Foo::Foo()
 : p(new Impl),
   bar(new Bar)
{
}

Foo::~Foo()
{
   delete p;
   delete bar;
}

bar.h

#ifndef BAR_H
#define BAR_H

struct Impl;

class Bar
{
public:
   Bar();
   ~Bar();

private:
   Impl* p;
};

#endif

Bar.cpp

#include <iostream>
#include "Bar.h"

struct Impl
{
   Impl()
   {
      std::cout << "Bar.cpp's Impl::Impl()" << std::endl;
   }
};

Bar::Bar()
 : p(new Impl)
{
}

Bar::~Bar()
{
   delete p;
}

main.cpp

#include <iostream>
#include "Foo.h"

int main (int argc, char const *argv[])
{
   std::cout << "==> main()" << std::endl;
   Foo* f = new Foo();
   std::cout << "<== main()" << std::endl;
   return 0;
}

Ответы [ 3 ]

4 голосов
/ 16 ноября 2009

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

3 голосов
/ 16 ноября 2009

G'day,

Не является поведением редактора ссылок по умолчанию для получения первого символа, который удовлетворяет требованиям, и прекращения поиска.

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

Редактировать: Я только что видел, что редактор ссылок в Solaris запрещает использование нескольких определений по умолчанию. На самом деле вам нужно использовать переключатель редактора ссылок "-z muldefs", чтобы разрешить связывание, чтобы продолжить множественные определения в объектах, используемых для установления закрытия исполняемого файла.

Edit2: Я заинтригован здесь, поскольку это должно быть помечено как предупреждение. Что произойдет, если вы добавите

-std=c++98 -pedantic-errors

в командной строке при сборке исполняемого файла?

2 голосов
/ 17 ноября 2009

Другие уже говорили об одном правиле определения, я думал, что я перезвоню в каком-то объяснении и реальном обходном пути.

Объяснение :

Я не буду объяснять правило единого определения, но объясню, почему компоновщик не жалуется. Когда вы используете шаблоны, каждый объект получает свою собственную std::vector<int> реализацию. Компоновщик просто подбирает первый доступный.

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

Работа вокруг :

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

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

namespace detail { class FooImpl; }

class Foo
{
  typedef detail::FooImpl Impl; // note that the typedef is private
  Impl* m_impl;
};

Просто и эффективно. Я всегда использую detail для деталей реализации и просто добавляю Impl в конец имени класса, для которого он должен быть Impl.

Примечания

  • Жаль, что вы не можете просто объявить об этом в классе, но мы ничего не можем с этим поделать.
  • namespace details предотвращает загрязнение основного пространства имен, в котором находится ваш класс, этими символами, что особенно удобно для автозаполнения IDE, поскольку в противном случае вы получите Foo и FooImpl в качестве предложений.
  • ImplFoo также не подходит для автозаполнения, поскольку все pimpl начинаются с Impl!

Надеется, что это поможет.

...