пятница, 3 июня 2011 г.

C++ шаблоны и multiple definition

На днях напоролся на проблему multiple definition при попытке компиляции файлов с шаблонами.



Схема проекта выглядела следующим образом:
// file header.h
#ifndef HEADER_H
#define HEADER_H

template< class T >
class TemplatedClass
{
     TemplatedClass( ParamType param );
};

#include "implementation.inl"

#endif
// end of file header.h

// file implementation.inl
#ifndef IMPLEMENTATION_INL
#define IMPLEMENTATION_INL

template<>
TemplatedClass::TemplatedClass<SpecialType>(ParamType param)
{
    // code here
}

#endif
// end of file implementation.inl

Всё было хорошо до тех пор, пока не появился проектик из двух cpp файлов, каждый из которых подключал файл header.h.
// file 1.cpp
#include <header.h>
...
// end of file 1.cpp
// file 2.cpp
#include <header.h>
...
// end of file 2.cpp
Вполне закономерно, в каждом .o файле этих .cpp файлов оказалось тело конструктора TemplatedClass::TemplatedClass<SpecialType>(ParamType param), что и привело к ошибке компоновки.
В этом можно убедиться двумя способами: 1) просмотрев имена функций в .o файлах с помощью unix-утилиты nm ; 2) добавив флаг компилятора -save-temps компилятора gcc и просмотрев получившиеся .ii файлы.
Поскольку тела шаблонных методов/функций должны быть известны на этапе компиляции конкретной цели (.o файла), т.е., при компиляции cpp-файла, файлы с такими телами нужно подключить с помощью #include. Но теперь уже из того файла, где реально нужно определение функции/метода, а не только объявление. Например,

// file header.h
#ifndef HEADER_H
#define HEADER_H

template< class T >
class TemplatedClass
{
     TemplatedClass( ParamType param );
};

#include "implementation.inl"
#endif
// end of file header.h

// file implementation.inl
#ifndef IMPLEMENTATION_INL
#define IMPLEMENTATION_INL

template<>
TemplatedClass::TemplatedClass<SpecialType>(ParamType param)
{
    // code here
}

#endif
// end of file implementation.inl


// file 1.cpp
#include <header.h>
...
// end of file 1.cpp
// file 2.cpp
#include <header.h>
#include <implementation.inl>
...
// end of file 2.cpp
 P.S. Возможно, при таких ошибках стоит пересмотреть состав заголовочных файлов - может быть, его можно разделить на несколько самостоятельных (лично у себя нашёл такой недочёт).

Комментариев нет: