воскресенье, 4 декабря 2011 г.

Шаблоны С++


Появилась желание написать шаблон класса, но когда я описал класс в файле .h, а реализовал методу в - .cpp, моя программа отказалась компилироваться. В поисках истины я решил скинуть всё в один файл main.cpp: и класс и его реализацию. И на моё удивление программа успешно откомпилировалась! С этого момента я начал поиски ответа на этот вопрос...

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

Ниже я привожу свободный перевод статьи
Why can't I separate the definition of my templates class from its declaration and put it inside a .cpp file?
__________________________________________________________________________

Почему я не могу отделить описание моего шаблона класса от его объявления и поместить его(описание) в отдельный .cpp файл?

Если вы просто хотите решить эту проблему, обратитесь к статьям [35.13] и [35.14].
Если вы хотите понять, почему это обстоит именно так и не иначе, то следует принять следущие факты:
  1. Шаблон - это не класс или функция. Шаблон - это "образец", который используется компилятором для генерации класса или функции.
  2. Чтобы компилятор сгенерировал код, он должен видеть как описание шаблона (не только его объявление), так и тип, который используется для заполнения шаблона. Например, если вы попытаетесь использовать Foo<int>, то компилятор должен видеть как шаблон Foo, так и ваше обращение Foo<int>.
  3. Ваш компилятор, вероятно, не запоминает детали одного cpp-файла, пока компилирует другой cpp-файл. Это возможно, но большинство компиляторов так не делают, и т.к. вы читаете эту инструкцию, ваш компилятор, очевидно, не запоминает этого. Кстати, это называется "модель раздельной компиляции" ("separate compilation model").
На основе этих фактов рассмотрим пример. Предположим, у нас есть шаблон Foo, описанный следующим образом:
template<typename T>
class Foo {
public:
    Foo();
    void someMethod(T x);
private:
    T x;
};
с соответствующим описание методов:
template<typename T>
Foo<T>::Foo()
{
    ...
}

template<typename T>
void Foo<T>::someMethod(T x)
{
    ...
}
Теперь предположим, что вы имеете некоторый код в Bar.cpp, который использует Foo<int>:
// Bar.cpp

void blah_blah_blah()
{
    ...
    Foo<int> f;
    f.someMethod(5);
    ...
}
Очевидно, что когда кто-то использует данный шаблон, это приводит к обращению описания конструктора класса и к описанию метода someMethod(), а также к строке Foo<int>, где указан тип T (в данном случае int).
Но если мы расположим описание конструктора и метода someMethod() в файл Foo.cpp, то во время компилирования Foo.cpp компилятор увидит шаблон класса, а во время компилирования Bar.cpp - увидит Foo<int>. Но никогда не будет момента, когда компилятор сможет одновременно увидеть как шаблона класса, так и вызов Foo<int>.
Исходя из правила #2 выше, будет невозможно сгенерировать код для метода Foo<int>::someMethod().
__________________________________________________________________________

Решение

В качестве решения я выбрал следующую модель организации файлов для шаблонов класса:
// MyClass-impl.cpp
// Описание класса
template <class T> MyClass<T>::MyClass()
{
    x = 0;
}
// MyClass.cpp
// Объявление класса
template <class T> class MyClass
{
public:
    MyClass();
    T x;
};
#include "MyClass-impl.cpp"
// MyClass.h
// Заголовочный файл
#ifndef MYCLASS_H
#define MYCLASS_H

#include "MyClass.cpp"

#endif // MYCLASS_H
// main.cpp
#include <iostream>
using namespace std;

#include "MyClass.h"

int main()
{
    MyClass<int> a;
    a.x = 10;
    cout << a.x;
    return 0;
}


Приведу ещё пример кода шаблона из этого же раздела под-статьи [35.20] Can the previous problem hurt me silently? Is it possible that the compiler will silently generate the wrong code?
Если вы не знакомы с шаблонами, но умеете писать классы, этот пример вас повергнет в лёгкий шок )
class Xyz { ... };  //< global ("namespace scope") type
void f() { }        //< global ("namespace scope") function

template<typename T>
class B {
public:
    class Xyz { ... };  //< type nested in class B<T>
    void f() { }        //< member of class B<T>
};

template<typename T>
class D : public B<T> {
public:
    void g()
    {
        Xyz x;  //< suprise: you get the global Xyz!!
        f();    //< suprise: you get the global f!!
    }
}; 
Шаблоны имеют другое поведение в отличии от классов.

1 комментарий:

  1. Не понятно, зачем делать три файла для задании шаблона (.h, .cpp, -impl.cpp), когда можно обойтись двумя (.h, .cpp)?
    В примерах по ссылкам impl.h нужен был лишь в случае невозможности редактирования .cpp и содержал явные инстанцирования шаблонов, у вас же h вообще не содержит полезной информации, сpp содержит декларацию, что уже странно, и потом пришлось добавлять impl.cpp уже с реализацией (который место б в cpp). Мне не очевидно - зачем такое решение?

    ОтветитьУдалить