2015-03-06 1 views
2

Рассмотрим следующую ситуацию я обнаружил сегодня, просматривая довольно большой кодовую (реальный источник, примером для примера):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: Я не запрашивают решение проблемы (это может быть исправлено с пространствами имен, или просто переименовать класс не общественную базу), я просто хочу знать первопричину :)

+0

Именно по этой причине были изобретены пространств имен. Что происходит, так это то, что все символы заканчиваются как «слабые» до последнего связывания с исполняемым файлом, и вы можете просто получить неправильное определение «выигрыш», что приводит к сбою. Причина в том, что линкер не может действительно знать, какой класс является «правильным», и все они выглядят одинаково. – Petesh

+0

Звучит как UB. Разве это не нарушает правило единого определения? Возможно, у вас нет нескольких ошибок определения, потому что они разные классы, но все же .. Вот почему язык поддерживает 'namespace', чтобы избежать таких проблем. –

+0

Я собирался спросить, загадочно ли вы загадочно исчезли, если вы закрепили определения классов в 'file2.cpp' в * анонимном пространстве имен. И я нахожу это совершенно удобным, что вы * не * реализовали 'Base :: ~ Base()' в файле file2.cpp, что/должно/должно/могло бы вызвать дубликат-символ во время ссылки в первую очередь. – WhozCraig

ответ

4

Как указано в комментариях, это нарушает одно правило определения, которое влечет за собой UB. Способ защиты от этого состоит в том, чтобы использовать пространства имен и, в частности, поставить любое определение частного класса (чтение внутри cpp-файла) внутри анонимного пространства имен.

EDIT: Извините, я просто понял, что WhozCraig уже обеспечили тот же ответ в комментариях

+0

Нет проблем, WhozCraig должен был знать, что ответы принадлежат как ответ, а не комментарий. fritzone не может принять комментарий как правильный ответ. – MSalters

+0

Действительно, очень неудобный способ нарушить одно правило определения. :) – fritzone

+0

@fritzone: Да, но не скомпрометированы в больших проектах. Я впервые попал в эту проблему, когда писал несколько тестов для программы, содержащей множество классов шаблонов. Для модульных тестов я написал пару различных 'struct test {}' в файлах ' _test.cpp' и получил ошибки повсюду, что не имело никакого смысла. – MikeMB