Рассмотрим следующую ситуацию я обнаружил сегодня, просматривая довольно большой кодовую (реальный источник, примером для примера):C++ класс сращивание, линкер вопрос
Одна из команд созданы следующие два файла и набор интерфейсов (MoreBase
, Base
). Все хорошо до этого момента.
* file1.h *
class MoreBase
{
public:
MoreBase();
virtual ~MoreBase();
};
class Base : public MoreBase
{
public:
virtual ~Base();
virtual void func() const = 0;
};
class A : public Base
{
public:
A();
~A();
virtual void func() const;
};
* file1.cpp *
#include <iostream>
#include "file1.h"
using namespace std;
MoreBase::MoreBase() { cout << "file 1 MoreBase::MoreBase " << (void*)this << endl; }
MoreBase::~MoreBase() { cout << "file 1 ~MoreBase::MoreBase " << (void*)this << endl;}
Base::~Base() { cout << "file 1 ~Base::Base " << (void*)this << endl; }
A::~A() { cout << "file1 ~A::A() "<< (void*)this << endl; }
A::A() { cout << "file 1 A::A() "<< (void*)this << endl; }
void A::func() const { cout << "file 1 A::func() "<< (void*)this << endl; }
Но есть еще одна команда, которая находится в совершенно другой отдел, здания, страны, континент, что-то совершенно иное ...
* file2.h *
int some2method();
* file2.cpp *
#include <iostream>
using namespace std;
class Base
{
public:
virtual ~Base();
virtual void something() const = 0;
};
class B : public Base
{
public:
B();
~B();
virtual void something() const;
};
B::~B() { cout << "file 2 B::~B() "<< (void*)this << endl; }
B::B() { cout << "file 2 B::B() "<< (void*)this << endl; }
void B::something() const { cout << "file 2 B::something() "<< (void*)this << endl; }
// VARIABLE
const Base& x = B(); // ***
int some2method()
{
x.something();
return 42;
}
* main.cpp *
#include "file2.h"
int main()
{
some2method();
}
И давайте компилировать это нравится:
$ g++ -ggdb main.cpp file1.cpp file2.cpp -o test
и бежим:
$ ./test
file 1 MoreBase::MoreBase 0x6022f0
file 2 B::B() 0x6022f0
file 2 B::something() 0x6022f0
file 2 B::~B() 0x6022f0
file 1 ~Base::Base 0x6022f0
file 1 ~MoreBase::MoreBase 0x6022f0
Вы не считаете странным, что он строит MoreBase
объект, который я никогда не просил?
К сожалению, (1) компания разрабатывает один продукт, поэтому все исходные файлы связаны в один исполняемый файл.
К сожалению (2) Base
является очень распространенным именем в области мы работаем ...
К счастью (1) линия, отмеченные // ***
должен был быть найден в some2method()
и приложение разбился при компиляции с GCC. Вот где я начал расследование (прямо сейчас он снаружи, поэтому даже не разбился).
И Очевидно, вопрос: Как это возможно? Почему линкер объединил два совершенно несвязанных класса, хотя у них есть несчастье использовать одно и то же имя. Это неопределенное поведение, определенное что-то или просто несчастье?
EDIT: Я не запрашивают решение проблемы (это может быть исправлено с пространствами имен, или просто переименовать класс не общественную базу), я просто хочу знать первопричину :)
Именно по этой причине были изобретены пространств имен. Что происходит, так это то, что все символы заканчиваются как «слабые» до последнего связывания с исполняемым файлом, и вы можете просто получить неправильное определение «выигрыш», что приводит к сбою. Причина в том, что линкер не может действительно знать, какой класс является «правильным», и все они выглядят одинаково. – Petesh
Звучит как UB. Разве это не нарушает правило единого определения? Возможно, у вас нет нескольких ошибок определения, потому что они разные классы, но все же .. Вот почему язык поддерживает 'namespace', чтобы избежать таких проблем. –
Я собирался спросить, загадочно ли вы загадочно исчезли, если вы закрепили определения классов в 'file2.cpp' в * анонимном пространстве имен. И я нахожу это совершенно удобным, что вы * не * реализовали 'Base :: ~ Base()' в файле file2.cpp, что/должно/должно/могло бы вызвать дубликат-символ во время ссылки в первую очередь. – WhozCraig