четвер, 28 липня 2011 р.

Як облаштувати сирцевий код шаблона

Введення
Часто мене запитували складно чи лекго програмувати з шаблонами. Відповідь яку я зазвичай давав така: “Це легко коли ти використовуєш, але складно коли створюєш їх”. Лиш погляньте на деякі бібліотеки шаблонів, які використовуються повсякденно, наприклад, STL, ATL, WTL, деякі бібліотеки з Boost, і ви побачите, що я маю на увазі під цим. Ці бібліотеки є чудовим прикладом принципу “простий інтерфейс – складна реалізація”.



Я почав використовувати шаблони п’ять років тому, коли я виявив шаблонні контейнери MFC, і до минулого року я не мав потреби в розробці власних шаблонів. Коли я кінцево дійшов висновку, що я потребую розробки деяких власних класів шаблонів, перше, що вразило мене був той факт, що “традиційний” шлях облаштування сирцевого коду (об’явлення в *.h файлах, і визначення в *.cpp файлах) не працює з шаблонами. Це зайняло в мене трохи часу, щоб зрозуміти в чому річ і знайти як обійти цю проблему.
Ця стаття спрямована на розробників, які достатньо добре знаються на шаблонах, щоб використовувати їх, але недостатньо досвідчені для розробки. Тут, я зачеплю лиш класи шаблони, а не функції шаблони, але принципи однакові в обох випадках.
Опис проблеми
Щоб показати проблему розглянемо приклад. Припустимо ми маємо клас шаблон array у файлі array.h.
// array.h 
template <typename T, int SIZE>
class array
{
    T data_[SIZE];
    array (const array& other);
    const array& operator = (const array& other);
public:
    array(){};
    T& operator[](int i) {return data_[i];}
    const T& get_elem (int i) const {return data_[i];}
    void set_elem(int i, const T& value) {data_[i] = value;}
    operator T*() {return data_;}
};
Також ми маємо файл main.cpp з кодом, що використовує масив:
// main.cpp 
#include "array.h"
int main(void)
{
    array<int, 50> intArray;
    intArray.set_elem(0, 2);
    int firstElem = intArray.get_elem(0);
    int* begin = intArray;
}
Це компілюється добре і робить саме те, що ми хочемо: спочатку ми створюємо масив 50 цілих, далі присвоюємо першому елементові 2, читаємо цей елемент і зрештою отримуємо вказівник на початок масиву.
Тепер, що станеться якщо ми спробуємо організувати код більш традиційним чином? Давайте спробуймо виділити код в array.h і подивитись, що вийде. Зараз ми маємо два файли: array.h і array.cpp (main.cpp залишається незмінним).
// array.h 
template <typename T, int SIZE>
class array
{
    T data_[SIZE];
    array (const array& other);
    const array& operator = (const array& other);
public:
    array(){};
    T& operator[](int i);
    const T& get_elem (int i) const;
    void set_elem(int i, const T& value);
    operator T*();
};
// array.cpp 
#include "array.h"
template<typename T, int SIZE>
T& array<T, SIZE>::operator [](int i) {
    return data_[i];
}
template<typename T, int SIZE>
const T& array<T, SIZE>::get_elem(int i) const {
    return data_[i];
}
template<typename T, int SIZE>
void array<T, SIZE>::set_elem(int i, const T& value) {
    data_[i] = value;
}
template<typename T, int SIZE> array<T, SIZE>::operator T*() {
    return data_;
}
Спробуйте скомпілювати це, і ви отримаєте три помилки компонувальника.
Постають такі питання:
  1. Чому взагалі з’явились ці помилки?
  2. Чом лише три помилки? Миж маємо чотири функції члена в array.cpp.
З метою відповіді на це запитання, ми маємо трошки заглибитись в деталі перебігу інстанціонування шаблонів.
Інстанціонування шаблонів
Одніює зі звичайних помилок програмістів є те, що коли вони працюють з класами шаблонами вони розглядають ці класи як типи. Певно термін параметризовані типи, який часто використовується для класів шаблонів, призводить до подібного мислення. Добре, класи шаблони це не типи, вони лише те, що говорить їх ім’я: шаблони. Існує декілька важливих концепцій для розуміння стосунків між класами шаблонами і типами:
  • Компілятор використовує класи шаблони для створення типів шляхом заміни параметрів шаблона,

    і цей процес зветься інстанціюванням.
  • Тип створений з класу шаблона зветься спеціалізацією.
  • Інстанціонування шаблона відбувається за запитом, це означає, що компілятор створює спеціалізацію тоді, коли знаходить її викоритсання в коді

    (це місце зветься точкою інстанціонування).
  • Для створення спеціалізації компілятор в точці інстанціонування має “бачити” не тільки

    об’явлення шаблона, але також визначення.
  • Інстанціонування шаблона ліниве, це значить, що інстанціонуються лиш функції, що використовуються.
Якщо ми повернемось до нашого приклад, array це шаблон, і array<int, 50> це спеціалізація шаблона – тип. Перебіг створення array<int, 50> з array це інстанціонування

Точка інстанціонування розташована в файлі main.cpp. Якщо ми облаштовуємо код за звичайним підходом, компілятор бачить об’явлення шаблона (array.h), але не визначення (array.cpp). Тож, компілятор буде нездатен створити тип array<int, 50>.

Однак, він е прозвітує про помилку: він вважтиме, що цей тип визначений в якійсь іншій одиниці компіляції і полишить це на компонувальник.
Тепер, що станеться з іншою одиницею компіляції (array.cpp)? Компілятор розбере визначення шаблона і перевірить синтакс на вірність, але не сгенерує код для функцій членів. Чому так? Для створення коду, компілятор має знати параматри шаблона – він потребує тип, а не шаблон.
Тож, компоновник не знайде визначення для array<int, 50> ані в main.cpp ані в array.cpp, звідси і виникають помилки про незнайдені визначення членів.
Добре. Це відповідь на питання 1. Але що з питанням 2? Ми маємо чотири функції члена визначені в array.cpp, і тільки три повідомлення про помилки видані компонувальником. Питання знаходиться в концепції лінивого інстанціонування. В main.cpp ми не використовуємо operator[] і компілятор навіть не намагався інстанціонувати це визначення.
Розв’язки
Тепер, коли ми розуміємо звідки витікає проблема, було б добре запропонувати якісь шляхи її ров’язання. Ось вони:
  1. Зробити визначення шаблона видимим в точці інстанціонування.
  2. Інстанціонувати необхідні типи в окремій одиниці компіляції, так щоб компонувальник міг їх знайти.
  3. Використати ключове слово export.
Перші два методи часто звуться моделлю включення, в той час як третій іноді згадується як модель відокремлення.
Перше рішення значить, що ми маємо включити не тільки об’явлення шаблона, але й визначення до кожної одиниці трансляції в якій ми використовуємо шаблони. В нашому випадку це означає, що ми будемо використовувати першу версію array.h з усіма вбудованими функціями членами, або ми включимо array.cpp в наш main.cpp. В цьому випадку, компілятор бачитиме і об’явлення, і визначення всіх функцій членів з array і буде здатен інстанціонувати array<int, 50>. Недоліком цього підходу є те, що наші одиниці компіляції можуть стати великими, і це значно збільшить час компіляції і компонування.
Зараз друге рішення. Ми можемо явно інстанціонувати шаблон для типів, які потребуємо. Найкраще тримати всі явні вказівки інстанціації в окремомій одиниці компіляції. Тут ми можемо додати новий файл templateinstantiations.cpp
// templateinstantiations.cpp 

#include "array.cpp"

template class array <int, 50>; // explicit instantiation
Тип array<int, 50> буде сгенеровано не в main.cpp але в templateinstantiations.cpp і компонувальник знайде визначення. В цьому піході ми уникаємо великих заголовкових файлів, і звідси час збирання зменшується. Також, заголовкові файли будуть “чистими” і більш читабельними. Однак, тут ми не маємо переваги лінивого інстанціонування (явне інстанціонування генерує код для всіх функцій членів), і це може стати складно підтримувати templateinstantiations.cpp для великих проектів.
Третє рішення полягає в позначенні визначень шаблона ключовим словом export і компілятор потурбується про інше. Коли я читав про export у книзі Страуструпа, я був сповненим натхнення стосовно цієї директиви.

Це зайняло у мене декілька хвилин, щоб віднайти, що вони нереалізована в VC 6.0, і трошки більше, щоб знайти, не існує компілятора з підтримкою цього ключового слова (перший компілятор з такою підтримкою вийшов у 2002). Відтоді, я багато прочитав про export і вивчив, що вона заледве може вирішити будь-які проблеми пов’язані із моделлю включення.
Висновок
При розробці бібліотек шаблонів, ми маємо розуміти, що класи шаблонів це не “звичайні типи” і, що ми маємо мислити інакше під час роботи з ними. Метою цієї статті було не злякати розробників, які бажають програмувати шаблони. Навпаки, я сподіваюсь, що це допоможе їм уникнути розповсюджених помилок, яких припускаються люди, які щойно приступили до розробки шаблонів.
Вихідна стаття

10 коментарів:

  1. Try walking without swinging your physiological aspects, the Medical residential area wide accepts the Dopamine deficiency theory.
    This mental picture is taken in Summerfield, with a just noticeable microseism in just now one hired man, and progresses to an eventual
    slowing or Freezing of movement. That organism said, On that point that don t own it,
    they develop it by and by upon disease onset. So
    what should one do when Christmas tomorrow dark, and yield
    them breakfast Christmas dawning.

    Here is my blog: Parkinson'S Disease Specialists Gravelly
    my website :: Parkinson'S disease specialists Gravelly

    ВідповістиВидалити
  2. transfer from passion. And all of the calories per cup is also a intelligent and fit way to set about your
    day.

    Feel free to visit my page - symptoms cholesterol hdl low levels
    my web page :: symptoms cholesterol hdl low levels

    ВідповістиВидалити
  3. Does that mean we will focus on the Revolution's website see below. Personas que fueron estafadas por mdicos experientes e os tutelados, estes desde que no aprendan ahora, sin diagnosticar durante muchos aos. However other options are improving the nutrition absorption ability of the symptoms of lupus include an efficient drug named as Prednisone. I was so angry. These day-to-day aches and flu-like symptoms. More information on the cheeks and nose and cheeks? And the earlier symptoms of Lupus, is produced in the mother's blood called anti-Ro.


    My web page: lupus doctor Broadwater

    ВідповістиВидалити
  4. Hello! I could have sworn I've been to this website before but after browsing through some of the post I realized it's new to me.
    Nonetheless, I'm definitely happy I found it and I'll be bookmarking and
    checking back often!

    Feel free to visit my site ... letmewatchthis

    ВідповістиВидалити
  5. Generally I do not read article on blogs,
    but I would like to say that this write-up very compelled me to take a look
    at and do so! Your writing taste has been amazed me.
    Thank you, very great article.

    Also visit my site :: free tv online

    ВідповістиВидалити
  6. Wonderful work! That is the type of information that
    are meant to be shared around the net. Shame on Google for now not positioning
    this put up upper! Come on over and consult with
    my web site . Thank you =)

    Here is my weblog :: tv online

    ВідповістиВидалити
  7. One should apply minimal pressure to lift these concrete grinders and also hold it in flat motion while working.
    Another benefit of rubber mulch versus typical mulch is its lifespan.
    For faster setting concrete, use less water,
    for more workability, use more water.

    ВідповістиВидалити
  8. Therefore, if you wanted to specifically look for
    CNN News, it is right there in its own directory on
    the front webpage. Your local newspapers are dynamic ways to
    boost your business in more far-reaching local coverage in your city.
    Several news reports, writers, and analysts play a crucial role in the development of global news undoubtedly.

    Generally Tuyetkegel Blognic could be consisted of
    any types such as social, economic, political, art, cultural,
    terrorism, suicides, religious, health, technology, science, business,
    marketing, and disaster news. The dishes and ingredients differ slightly from region to region,
    with each region having their own specialties.



    Also visit my web site ... click here

    ВідповістиВидалити
  9. If you are like me and love garlic, you are already too late.
    Native plants or at least plants adapted
    to similar conditions as your region will have a better chance of
    growing and thriving. Organic gardening teaches kids about the
    value of hard work and its rewards.

    Here is my page sozzled

    ВідповістиВидалити
  10. They need to see doctor for some advices on how they can take control of their blood pressure.
    An image will help flesh out exactly what you’re trying to announce while adding character, flavor and visual appeal to the document.
    Her book doesn't just give a temporary quick fix, but provides all the information necessary for understanding, combating and ultimately curing yeast infection. If you suspect that your method has become infected there are a number of points you can do yourself to try to get rid of the offending application. Sara Gilbert shared about her new love on her own talkshow, “All these article are out that I'm in a new relationship.



    Check out my website; manufactor

    ВідповістиВидалити