На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела FAQ в группе разделов С++.
1. Раздел FAQ предназначен для публикации готовых статей.
2. Здесь нельзя задавать вопросы, для этого существуют соответствующие разделы:
Чистый С++
Visual C++ / MFC / WTL / WinApi
Borland C++ Builder
COM / DCOM / ActiveX / ATL
Сопутствующие вопросы
3. Внимание, все темы и сообщения в разделе премодерируются. Любое сообщение или тема будут видны остальным участникам только после одобрения модератора.
Модераторы: B.V., Qraizer
  
> Шаблоны: unresolved external symbol , описание и реализация шаблонов
    Товарищ Железный Феликс (aka Flex Ferrum), по моим оценкам ответил раз тридцать на этот вопрос, и, тем не менее, вопрос этот периодически возникает. Чаще всего его задают новички, но иногда и опытные программисты попадают в этот капкан.

    Кому не в лом, с обсуждением этой темы и предложенными решениями можно ознакомиться по следующим, напр. ссылкам:
    шаблонный класс
    Проблема с шаблоном
    шаблонные классы
    Шаблонные функции
    Шаблоны (templates) в С++
    LNK ошибка при шаблонном наследовании
    шаблонные члены класса
    ошибки при линковке библиотк с шаблонами
    шаблоны
    error LNK2019: unresolved external symbo
    Вопрос по шаблонам
    template's
    [список может быть продолжен]

    Итак, проблема:
    имеем в проекте, к примеру три файла file.h, file.cpp, main.cpp
    /* file.h - описание шаблона */
    ExpandedWrap disabled
      template <class T> class C
         {
         public :
         C (void);
         private :
         T t;
         };

    /* file.cpp - определение функций шаблона */
    ExpandedWrap disabled
      #include "file.h"
       
      template <class T> C<T> :: C (void): t (1) {;}


    /* main.cpp - использование шаблона */
    ExpandedWrap disabled
      #include "file.h"
       
      C<float> c;
       
      int main (int, char *[])
        {
        return 0;
        }


    Файлы file.cpp и main.cpp транслируются раздельно. Пытаемся собрать проект, - и на этапе компоновки
    линкер нам сообщает о том, что какой-то symbol оказался unresolved. Сообщение об этой ошибке может
    зависеть от среды разработки, но в общем суть его одна.

    В чём же тут проблема ?
    А проблема в том, дорогие товарищи, что в Европе кофе не растёт.
    Большинство наиболее распространённых и наиболее популярных компиляторов не поддерживает это средство языка,
    которое называется экспорт шаблонов.
    В их числе:
    - Компиляторы Borland C++ (все версии всех годов, правда про Builder 2006 не могу точно сказать, но
    скорее всего и он тоже не поддерживает);
    - Microsoft C++ (тоже самое, ни один из них не поддерживает);
    - GNU gcc/g++ (тоже ни одна из версий не поддерживает);

    Менее распространённые, но которые мне проходилось использовать:
    - QNX qcc (все версии);

    Компиляторы для встраиваемых приложений:
    - IAR EWB (все поддерживаемые процессорные ядра/устройства);
    - Green Hills Multi2000;
    - Analog Devices Visual DSP++ (все версии);
    - Texas Instruments Code Composer;
    [список может быть продолжен]

    В некоторых средах разработки, например таких как Microsoft VS, Green Hills, или GNU gcc об этом
    явно сообщается в документации.

    Из мне известных компиляторов, которые могут осилить такой код, могу назвать только
    - Intel C++ (версии, выпущеные позже октября 2004 г.)
    [список может быть продолжен]


    Как решать эту проблему ?
    Наиболее простой ответ: на этапе трансляции описание шаблона, определение шаблона, и его
    использование должно находится в одной единице трансляции. На примере вышеприведённого кода можно
    сделать так :
    /* file.h - описание шаблона */
    ExpandedWrap disabled
      template <class T> class C
         {
         public :
         C (void) : t (1) {;}
         private :
         T t;
         };

    /* main.cpp - использование шаблона */
    ExpandedWrap disabled
      #include "file.h"
       
      C<float> c;
       
      int main (int, char *[])
        {
        return 0;
        }

    определение шаблонного метода выкидываем из file.cpp

    или вот так, если в лом переписывать заголовок:

    /* file.h - описание шаблона */
    ExpandedWrap disabled
      template <class T> class C
         {
         public :
         C (void);
         private :
         T t;
         };

    /* file.cpp - определение функций шаблона */
    ExpandedWrap disabled
      #include "file.h"
       
      template <class T> C<T> :: C (void): t (1) {;}


    /* main.cpp - использование шаблона */
    ExpandedWrap disabled
      #include "file.h"
      #include "file.cpp" /* брррр...... ахтунг, но работать будет*/
       
      C<float> c;
       
      int main (int, char *[])
        {
        return 0;
        }


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

    /* file.h - описание шаблона */
    ExpandedWrap disabled
      template <class T> class C
         {
         public :
         C (void);
         private :
         T t;
         };
       
      template <> C <float> :: C (void);

    /* file.cpp - определение функций шаблона */
    ExpandedWrap disabled
      #include "file.h"
       
      template <> C<float> :: C (void): t (3.1415) {;}

    /* main.cpp - использование шаблона */
    ExpandedWrap disabled
      #include "file.h"
       
      C<float> c;
       
      int main (int, char *[])
        {
        return 0;
        }


    Такой вариант тоже работоспособен.


    В догон. Какие шаблоны практически удаётся скомпилять в раздельных
    единицах трансляции? С применением вышеупомянутого Intel C++ мне удалось
    скомпилировать шаблонную функцию объявленную в одном файле, а определённую в другом.
    Как это выглядит (icc 8.1, Linux FC2)

    /* file.cpp - определение функций шаблона */
    ExpandedWrap disabled
      #include <stdio.h>
      #include <typeinfo>
       
      export template <class T> T f (T a)
        {
        printf ("type of v : %s\n", typeid (a).name ());
        return a + 1;
        }


    /* main.cpp - использование шаблона */
    ExpandedWrap disabled
      export template<class T> T f (T);
       
      float s = 3.1415;
      double d = 2.71828;
      const char * c = "hello world\n";
       
      int main ()
        {
        f (s);
        f (d);
        f (c);
        return 0;
        }


    командная строка:

    [user@work template]$ icpc -export -export_dir ./ -c file.cpp
    [user@work template]$ icpc -export -export_dir ./ -c main.cpp
    [user@work template]$ icpc -export -export_dir ./ file.o main.o -o test
    [user@work template]$ ./test
    type of v : f
    type of v : d
    type of v : PKc



    ну и так должен выглядеть правильно код, приведённый в самом начале темы,
    что бы он был правильно понят компилятором:

    /* file.h - описание шаблона */
    ExpandedWrap disabled
      template <class T> class C
         {
         public :
         C (void);
         private :
         T t;
         };
       
      export template <class T> C <T> :: C (void);

    /* file.cpp - определение функций шаблона */
    ExpandedWrap disabled
      #include <stdio.h>
      #include <typeinfo>
       
      export template <class T> C<T> :: C (void) : t (1.4142)
        {
        t += 1;
        printf (" v = %f, type = %s\n", t, typeid (t).name ());
        }


    /* main.cpp - использование шаблона */
    ExpandedWrap disabled
      #include "file.h"
       
      C<float> c;
       
      int main (int, char *[])
        {
        return 0;
        }


    командная строка :
    [user@work template]$ icpc -export -export_dir ./ -c file.cpp
    [user@work template]$ icpc -export -export_dir ./ -c main.cpp
    [user@work template]$ icpc -export -export_dir ./ file.o main.o -o test
    [user@work template]$ ./test
    v = 2.414200, type = f

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

    Успехов всем!
    Сообщение отредактировано: antigen -
      Цитата antigen @
      Из мне известных компиляторов, которые могут осилить такой код, могу назвать только
      - Intel C++ (версии, выпущеные позже октября 2004 г.)
      [список может быть продолжен]

      Я бы отнесстя к поддержке такой возможности весьма скептически...
      Может, лишь самые простейшие случаи...
        Цитата BioUnit @
        Я бы отнесстя к поддержке такой возможности весьма скептически...
        Может, лишь самые простейшие случаи...


        Совершенно верно. Хелловорлд работает, а чо чуть посложнее - уже не компилируется.
        Возможно в более свежих весриях эта поддержка будет улучшена.
          Цитата antigen @
          второй вариант: частичная специализация. Если известо, какой тип будет параметром заполнения шаблона,
          то можно явно сгенерить его методы в отдельной единице трансляции:
          /* file.h - описание шаблона */
          ExpandedWrap disabled
            template <class T> class C
               {
               public :
               C (void);
               private :
               T t;
               };
            template <> C <float>::C(void);

          template <> C <float>::C(void) это НЕ частичная специализация, а явная(полная) специализация члена(конструктора в данном случае). Более красиво вариант 2 реализуется директивой явного инстанцирования.
          Обобщю немного сказанное, существует несколько способов организации исходного кода шаблонов:
          1. Наиболее популярный подход - модель включения(inclusion model), определения шаблонов полностью размещаются в заголовочном файле.
          2. Модель явного инстанцирования(explicit instantiation model), как правило реализуется директивой явного инстанцирования (explicit instantiation directive).
          3. Модель разделения шаблонов(separation model), реализуется с помощью export.
          Сообщение отредактировано: T0ad -
            Почему разработчики компиляторов не торопятся с поддержкой экспорта шаблонов. Герб Саттер рассказывает какие трудности возникают при решении этой задачи.
              Возможность разносить описание шаблона и его использование по разным единицам трансляции так же предоставляет сановский компилятор (входящий в состав Sun Studio). Правда на ключевое слово "export" компилятор кладёт толстый болт, выдавая варнинг - "Export ignored -- not yet implemented.", но при этом сама такая возможность (определение шаблона - в заголовке, описание шаблона - в одном файле, использование - в другом) - реализована.
              Сообщение отредактировано: antigen -
                Цитата antigen @
                (определение шаблона - в заголовке, описание шаблона - в одном файле, использование - в другом)
                Ты, наверно, имел в виду, "объявление шаблона - в заголовке, определение шаблона - в одном файле, использование - в другом"? B.V., кажись, 8-ая фаза с её 3-им способом в действии.
                  Цитата Qraizer @
                  определение шаблона - в одном файле, использование - в другом

                  Да, да, именно так. И именно в такой форме, в какой с ней сталкиваются чаще всего:
                  ExpandedWrap disabled
                    /* file.h:*/
                    template <class T> T func (T);
                    /* end of file.h */
                     
                    /* file.cpp: */
                    #include "file.h"
                     
                    template <class T> T func (T t)
                       {
                       return t + t;
                       }
                     
                    /* end of file.cpp */
                     
                    /* main.cpp: */
                    #include "file.h"
                     
                    int main ()
                        {
                        float f;
                        double d;
                        func (f); // OK - Sun CC
                        func (d); // OK - Sun CC
                        }
                    /* end of main.cpp */
                    Цитата Qraizer @
                    B.V., кажись, 8-ая фаза с её 3-им способом в действии.

                    Компилить на левом компиляторе только ради этой штуки -- да ну. Остановился на явных инстанцированиях. Не так изящно и универсально, но проще, чем переходить на Sun'овский или Intel'овский компиль
                      B.V., ну я не в том смысле, мол, хватай и пользуй. Я в том, что всё-таки есть такие. Хотя юы один.
                      0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                      0 пользователей:


                      Рейтинг@Mail.ru
                      [ Script execution time: 0,0433 ]   [ 16 queries used ]   [ Generated: 28.03.24, 16:41 GMT ]