Re[4]: шаблонный виртуальный метод
От: B0FEE664  
Дата: 30.06.15 10:57
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Ты так себе видишь процесс компиляции? Если нет, то распиши, пожалуйста, свою схему по-подробнее с точки зрения компилятора: вот мы взяли файл a.cpp (с базовым классом) и на выходе получили такой-то код для таких сигнатур + различные вспомогательные таблицы. Потом взяли файл b.cpp (с наследником) и на выходе получили такой-то код для других сигнатур и другие таблицы. Потом запускам linker и собираем программу. Вот и опиши, что должен делать linker и что должно быть в коде и в таблицах чтобы программа собралась. Для классических виртуальных функций такие действия просты и известны. Для экспорта шаблонов известна пара костылей, которая нормально так и не используется. А что будет в твоём случае?


Вот так я вижу один из возможных вариантов:
// file: A.hpp
class A
{
  public:
    template <class T>
    virtual void virt_fun(T t)
    {
        std::cout << "A::" << t << std::endl;
    }
};


// file: B.hpp
#include "A.hpp"

class B : public A
{
  public:
    template <class T>
    virtual void virt_fun(T t)
    {
        std::cout << "B::" << t << std::endl;
    }
};


// file: C.cpp
#include "A.hpp"

void fun_str(A* p)
{
    p->virt_fun("fun1");
}
// компиляция этого кода инстанцирует метод A::virt_fun<const char*>



// file: D.cpp
#include "A.hpp"

void fun_int(A* p)
{
    p->virt_fun(32);
}
// компиляция этого кода инстанцирует метод A::virt_fun<int>


// file: E.cpp
#include "B.hpp"

void fun_float()
{
    B b;
    b.virt_fun(1.1f);
}
// компиляция этого кода инстанцирует метод B::virt_fun<float>, но не метод A::virt_fun<float>


// file: M.cpp
#include "B.hpp"

extern void fun_str(A* p);
extern void fun_int(A* p);
extern void fun_float();

int main(int argc, char* argv[])
{
    Test oTest;

    A* arr[] = {new A, new B};

    for(A* p : arr)
    {
        fun_str(p);
        fun_int(p);
    }
    fun_float();
}
// Компиляция этого кода не инстанцирует ни одного метода virt_fun.
// Таким образом во всех скомпилированных файлах есть только одна реализация для B::virt_fun и это B::virt_fun<float> в E.obj,
// однако,
// известно, что для каждого инстанцированного метода A::virt_fun должн быть инстанцирован соответствующий B::virt_fun метод.
// Информация о том, какие A::virt_fun методы инстанцированы лежат в объектниках C.obj, D.obj и E.obj, но эта информация не доступна
// здесь, в M.obj.

// На стадии линковки, линковщик создаёт таблицу виртуальных функций virt_fun для класса A, для этого он ищет во всех объектниках 
// все инстанцирования методов A::virt_fun и находит:
// A::virt_fun<const char*> в C.obj
// A::virt_fun<int>         в D.obj
// таким образом, таблица виртуальных функций для класса A готова.
// Далее, исходя из информации о структуре класса B линковщик знает, что для каждой инстанцированой функции A::virt_fun должна
// существовать соответствующая функция B::virt_fun. Линковщик ищет реализацию B::virt_fun<const char*> и B::virt_fun<int>, но не 
// находит, поэтому сообщает программисту, что реализации этих функций не найдены.
// Видя такое дело, программист создаёт ещё один файл, в котором пишет код заставляющий компилятор создать указанные реализации,
// которых не хватает. Что это за код - сейчас стандартом не определено, но можно предложить, например, такой синтаксис:

// file: B_impl.cpp
#include "B.hpp"

//virtual
void B::virt_fun<const char*>(const char*) = default;

//virtual
void B::virt_fun<int>(int) = default;
// компиляция этого файла инстанцирует метод B::virt_fun<const char*> и метод B::virt_fun<int>


// После компиляции программист перезапускает линковку добавив B_impl.obj в параметры.
// На стадии поиска реализаций B::virt_fun линковщик находит:
// B::virt_fun<const char*> в B_impl.obj
// B::virt_fun<int>         в B_impl.obj
// B::virt_fun<float>       в E.obj
//
// Таким образом у линковщика есть вся необходимая информация для построения таблиц виртуальных функций и реализация этих функций:

// Для класса A:
//   A::virt_fun<const char*> в C.obj
//   A::virt_fun<int>         в D.obj
// Для класса B:
//   B::virt_fun<const char*> в B_impl.obj
//   B::virt_fun<int>         в B_impl.obj
//   B::virt_fun<float>       в E.obj


Вроде бы ничего экстраординарного. Я что-то упускаю? (ну, кроме dll и so, конечно)
И каждый день — без права на ошибку...
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.