Является ли запрещение виртуальных функций шаблона ненужной осторожностью? - PullRequest
2 голосов
/ 01 мая 2019

Прочитав много постов на похожие темы и немного подумав, я все равно не понимаю, почему запрещено реализовывать виртуальные функции шаблона. На мой взгляд, этот случай не имеет ничего общего со смешиванием статического полиморфизма с динамическим, но он скорее использует шаблонную дифференциацию функций во время компиляции, а затем использует динамический полиморфизм для каждой отдельной созданной функции во время выполнения. время.

Рассмотрим этот кусок кода:

class parrent{
public:
    virtual float function(float value)const{
        return value;
    }
    virtual double function(double value)const{
        return value;
    }
    virtual long double function(long double value)const{
        return value;
    }
    virtual ~parrent() = default;
};
class a_child:public parrent{
public:
    float function(float value)const override{
        return value + 1.5;
    }
    double function(double value)const override{
        return value + 1.5;
    }
    long double function(long double value)const override{
        return value + 1.5;
    }
};

Очевидно, что этот код в порядке и достигнет ожидаемого результата. Но с помощью шаблона переписать похожий код:

class parrent{
public:
    template<typename t__>
    virtual t__ function(t__ value)const{
        return value;
    }
    virtual ~parrent() = default;
};
class a_child:public parrent{
public:
    template<typename t__>
    t__ function(t__ value)const override{
        return value + 1.5;
    }
};

Не допускается.

Я не дизайнер компиляторов, но из того, что я прочитал, компиляторы создадут справочную таблицу из виртуальных функций и используют их для запуска соответствующей функции во время выполнения, что отличается от того, что они делают в случае функций шаблона , Для любых наборов параметров шаблона, заданных для использования функции шаблона во время компиляции, компилятор создаст уникальную функцию. В этом примере компилятор может обнаружить параметры шаблона во время компиляции, просто взглянув на то, как эта функция виртуального шаблона использовалась во всей программе. Пожалуйста, рассмотрите основную функцию сейчас:

int main() {
parrent* a;
parrent* b;
a = new parrent;
b = new a_child;
std::cout<< a->function(1.6f) << std::endl;
std::cout<< a->function(1.6) << std::endl;
std::cout<< a->function(1.6L) << std::endl;
std::cout<< b->function(1.6f) << std::endl;
std::cout<< b->function(1.6) << std::endl;
std::cout<< b->function(1.6L) << std::endl;
delete a;
delete b;
return 0;
}

Здесь компилятор увидит, что функция использовалась один раз для значения с плавающей запятой, один раз для двойного значения и один раз для длинного двойного значения, поэтому в любом случае он может легко создать правильную функцию с соответствующими параметрами шаблона. И, наконец, будет 3 отдельных виртуальных функции, а не только одна виртуальная функция. Если у нас есть функция, параметры шаблона которой не могут быть выведены из входных данных функций, таких как

template<typename t__>
virtual t__ function(int value){return value;}

Тогда пользователи могут сами задать параметры, например:

object_pointer->function<double>(1234);

Эти методы - то, что уже используется в случае каких-либо шаблонных функций, так почему они могут отличаться для виртуальных функций!

единственное предостережение к этой практике, о котором я могу думать, это когда экземпляр виртуальной функции шаблона создается из дочернего объекта, а не из родительского объекта или указателя. Ну, даже в этом случае такая же практика может быть применена для создания различных виртуальных функций. В качестве альтернативы из-за недостатка использования их виртуальности они могут стать нормальными индивидуальными функциями.

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

Я полагаю, что упомянутая проблема в ответах связана с тем, что компилятор и / или компоновщик не могут знать, сколько (и какого типа) таблиц он должен создать для класса в отношении остальных кодов или других единицы перевода, с которыми он может столкнуться.

Хорошо, допустим, что он может создать незаконченный список таблиц и расширять его по мере продвижения. Проблема получения двух виртуальных таблиц или двух разных экземпляров одного и того же класса в случае динамического связывания уже может возникнуть при создании экземпляра класса шаблона с помощью виртуальной (не шаблонной) функции. Похоже, у компиляторов уже есть способ обойти эту проблему!

Во-первых, давайте не будем забывать, что в отношении c методы или классовые нестатические функции являются не чем иным, как простыми функциями, для которых объект является одним из их параметров, поэтому давайте не будем думать о классе как о каком-то сложном фрагменте кода.

Во-вторых, давайте не будем увлекаться тем, как компиляторы и компоновщики и что не работает сегодня. Язык должен быть стандартным, а не так, как компиляторы создают исполняемый файл! Давайте не будем забывать, что в стандарте c ++ 17 все еще есть много функций, которые даже GCC не охватывает!

Пожалуйста, объясните мне с точки зрения логики, как работают компиляторы и / или компоновщики, в чем проблема?

Ответы [ 2 ]

4 голосов
/ 01 мая 2019

Компиляторы реализуют полиморфные классы следующим образом: компилятор просматривает определение класса, определяет, сколько записей vtable необходимо, и статически назначает одну запись в этой vtable каждому из виртуальных методов класса.Где бы ни вызывался один из этих виртуальных методов, компилятор генерирует код, который извлекает vptr из класса и ищет запись со статически назначенным смещением, чтобы определить адрес, который должен быть вызван.

Мы можемТеперь посмотрим, как наличие виртуального шаблона может вызвать проблемы.Предположим, у вас есть класс, содержащий виртуальный шаблон.Теперь, после окончания определения класса, компилятор не знает, насколько велика возможность создания vtable.Он должен ждать до конца модуля перевода, чтобы увидеть полный список специализаций шаблона, которые фактически вызываются (или на которые берется указатель на член).Если класс определен только в этом единственном модуле перевода, эту проблему можно решить, назначив смещения vtable специализациям шаблона в некотором возрастающем порядке, в котором они встречаются, а затем выпустив vtable в конце.Однако, если у класса есть внешняя связь, это нарушается, как при компиляции различных модулей перевода, компилятор не может избежать конфликтов при назначении смещений специализациям шаблона виртуального метода.Вместо этого смещения vtable должны быть заменены символами, которые будут разрешены компоновщиком, как только он увидит список ссылочных специализаций из всех единиц перевода и объединит их в один список.Кажется, что если бы стандартный C ++ требовал поддержки виртуальных шаблонов, каждая реализация должна была бы требовать компоновщика для реализации этой функциональности.Я могу предположить, что это не будет осуществимо в ближайшее время.

1 голос
/ 01 мая 2019

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

Если у вас есть функция-член виртуального шаблона, например

template<typename t__>
virtual t__ function(t__ value)const{
    return value;
}

нет конца типам, для которых это применимо. Как компилятор узнает, стоит ли останавливаться на int и double? Существует неограниченное количество типов, для которых может быть реализована эта функция. Ожидаете ли вы, что компилятор сгенерирует vtable, который учитывает все возможные способы создания этой функции? Это бесконечно. Это не выполнимо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...