Может ли шаблон функции члена класса быть виртуальным? - PullRequest
278 голосов
/ 01 марта 2010

Я слышал, что шаблоны функций-членов класса C ++ не могут быть виртуальными. Это правда?

Если они могут быть виртуальными, каков пример сценария, в котором можно было бы использовать такую ​​функцию?

Ответы [ 11 ]

300 голосов
/ 01 марта 2010

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

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

Тем не менее, существует несколько мощных и интересных методов, связанных с сочетанием полиморфизма и шаблонов, в частности так называемое стирание типа .

113 голосов
/ 30 декабря 2014

Из шаблонов C ++ Полное руководство:

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

32 голосов
/ 01 марта 2010

C ++ не разрешает виртуальные функции-члены шаблона прямо сейчас. Наиболее вероятной причиной является сложность его реализации. Раджендра дает веские причины, почему это нельзя сделать прямо сейчас, но это может быть возможно при разумных изменениях стандарта. Особенно трудно определить, сколько экземпляров шаблонной функции действительно существует, и создание виртуальной таблицы кажется трудным, если учесть место вызова виртуальной функции. У людей, занимающихся стандартами, сейчас есть много других дел, и C ++ 1x - это тоже большая работа для авторов компиляторов.

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

16 голосов
/ 11 ноября 2011

Таблицы виртуальных функций

Давайте начнем с некоторого фона виртуальных таблиц функций и того, как они работают ( source ):

[20.3] В чем разница между виртуальным и не виртуальным функции-члены называются?

Не виртуальные функции-члены разрешаются статически. Это функция-член выбирается статически (во время компиляции) на основе тип указателя (или ссылки) на объект.

Напротив, виртуальные функции-члены разрешаются динамически (при время выполнения). То есть функция-член выбирается динамически (при времени выполнения) в зависимости от типа объекта, а не от типа указатель / ссылка на этот объект. Это называется «динамическое связывание». Большинство компиляторов используют какой-либо вариант следующей техники: если объект имеет одну или несколько виртуальных функций, компилятор помещает скрытые указатель на объект, называемый «виртуальный указатель» или «v-указатель». это v-указатель указывает на глобальную таблицу, называемую «виртуальная таблица» или "V-таблицы."

Компилятор создает v-таблицу для каждого класса, который имеет хотя бы один виртуальная функция. Например, если класс Circle имеет виртуальные функции для draw () и move () и resize () будет ровно одна v-таблица ассоциируется с классом Circle, даже если там был круг gazillion объекты, и v-указатель каждого из этих объектов круга будет указывать К Кругу V-стол. Сам V-стол имеет указатели на каждый из виртуальные функции в классе. Например, V-таблица Circle будет иметь три указателя: указатель на Circle :: draw (), указатель на Circle :: move () и указатель на Circle :: resize ().

Во время отправки виртуальной функции система времени выполнения следует v-указатель объекта на v-таблицу класса, затем следует соответствующий слот в v-таблице для кода метода.

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


Моя проблема, или как я сюда попал

Сейчас я пытаюсь использовать что-то подобное для базового класса cubefile с шаблонно оптимизированными функциями загрузки, которые будут реализованы по-разному для разных типов кубов (некоторые сохраняются по пикселям, некоторые по изображению и т. Д.).

Код:

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

Я бы хотел, чтобы он был, но он не скомпилируется из-за виртуальной шаблонной комбинации:

template<class T>
    virtual void  LoadCube(UtpBipCube<T> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

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

Решение

предупреждение, это не очень красиво, но оно позволило мне удалить повторяющийся код выполнения

1) в базовом классе

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

2) и в детских классах

void  LoadCube(UtpBipCube<float> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

template<class T>
void  LoadAnyCube(UtpBipCube<T> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1);

Обратите внимание, что LoadAnyCube не объявлен в базовом классе.


Вот еще один ответ о переполнении стека с обходным решением: нужен обходной путь к виртуальному шаблону .

12 голосов
/ 24 мая 2010

Следующий код можно скомпилировать и запустить правильно, используя MinGW G ++ 3.4.5 в Windows 7:

#include <iostream>
#include <string>

using namespace std;

template <typename T>
class A{
public:
    virtual void func1(const T& p)
    {
        cout<<"A:"<<p<<endl;
    }
};

template <typename T>
class B
: public A<T>
{
public:
    virtual void func1(const T& p)
    {
        cout<<"A<--B:"<<p<<endl;
    }
};

int main(int argc, char** argv)
{
    A<string> a;
    B<int> b;
    B<string> c;

    A<string>* p = &a;
    p->func1("A<string> a");
    p = dynamic_cast<A<string>*>(&c);
    p->func1("B<string> c");
    B<int>* q = &b;
    q->func1(3);
}

и вывод:

A:A<string> a
A<--B:B<string> c
A<--B:3

А позже я добавил новый класс X:

class X
{
public:
    template <typename T>
    virtual void func2(const T& p)
    {
        cout<<"C:"<<p<<endl;
    }
};

Когда я пытался использовать класс X в main (), вот так:

X x;
x.func2<string>("X x");

g ++ сообщает о следующей ошибке:

vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'

Итак, очевидно, что:

  • Виртуальная функция-член может использоваться в шаблоне класса. Компилятору легко построить vtable
  • Невозможно определить функцию-член шаблона класса как виртуальную, как вы можете видеть, сложно определить сигнатуру функции и выделить записи vtable.
11 голосов
/ 02 августа 2016

Нет, они не могут. Но:

template<typename T>
class Foo {
public:
  template<typename P>
  void f(const P& p) {
    ((T*)this)->f<P>(p);
  }
};

class Bar : public Foo<Bar> {
public:
  template<typename P>
  void f(const P& p) {
    std::cout << p << std::endl;
  }
};

int main() {
  Bar bar;

  Bar *pbar = &bar;
  pbar -> f(1);

  Foo<Bar> *pfoo = &bar;
  pfoo -> f(1);
};

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

6 голосов
/ 01 марта 2010

Нет, функции-члены шаблона не могут быть виртуальными.

3 голосов
/ 28 сентября 2011

Чтобы ответить на вторую часть вопроса:

Если они могут быть виртуальными, то каков пример сценария, в котором можно было бы использовать такую ​​функцию?

Это не лишено смысла хотеть сделать. Например, Java (где каждый метод является виртуальным) не имеет проблем с универсальными методами.

Одним из примеров C ++, когда требуется шаблон виртуальной функции, является функция-член, которая принимает универсальный итератор. Или функция-член, которая принимает объект универсальной функции.

Решением этой проблемы является использование стирания типов с boost :: any_range и boost :: function, что позволит вам принимать универсальный итератор или функтор без необходимости превращать вашу функцию в шаблон.

2 голосов
/ 29 марта 2017

Существует обходной путь для «метода виртуального шаблона», если набор типов для метода шаблона известен заранее.

Чтобы показать идею, в приведенном ниже примере используются только два типа (int и double).

Там «виртуальный» метод шаблона (Base::Method) вызывает соответствующий виртуальный метод (один из Base::VMethod), который, в свою очередь, вызывает реализацию метода шаблона (Impl::TMethod).

Нужно только реализовать шаблонный метод TMethod в производных реализациях (AImpl, BImpl) и использовать Derived<*Impl>.

class Base
{
public:
    virtual ~Base()
    {
    }

    template <typename T>
    T Method(T t)
    {
        return VMethod(t);
    }

private:
    virtual int VMethod(int t) = 0;
    virtual double VMethod(double t) = 0;
};

template <class Impl>
class Derived : public Impl
{
public:
    template <class... TArgs>
    Derived(TArgs&&... args)
        : Impl(std::forward<TArgs>(args)...)
    {
    }

private:
    int VMethod(int t) final
    {
        return Impl::TMethod(t);
    }

    double VMethod(double t) final
    {
        return Impl::TMethod(t);
    }
};

class AImpl : public Base
{
protected:
    AImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t - i;
    }

private:
    int i;
};

using A = Derived<AImpl>;

class BImpl : public Base
{
protected:
    BImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t + i;
    }

private:
    int i;
};

using B = Derived<BImpl>;

int main(int argc, const char* argv[])
{
    A a(1);
    B b(1);
    Base* base = nullptr;

    base = &a;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;

    base = &b;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;
}

Выход:

0
1
2
3

NB: Base::Method на самом деле излишек для реального кода (VMethod может быть обнародован и использоваться напрямую). Я добавил его, чтобы он выглядел как фактический «виртуальный» метод шаблона.

0 голосов
/ 17 августа 2018

В других ответах предложенная функция шаблона представляет собой фасад и не дает никакой практической выгоды.

  • Функции шаблона полезны для написания кода только один раз, используя Различные типы.
  • Виртуальные функции полезны для общего интерфейса для разных классов.

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

Однако для каждой комбинации типов шаблонов необходимо определить фиктивную функцию виртуальной оболочки:

#include <memory>
#include <iostream>
#include <iomanip>

//---------------------------------------------
// Abstract class with virtual functions
class Geometry {
public:
    virtual void getArea(float &area) = 0;
    virtual void getArea(long double &area) = 0;
};

//---------------------------------------------
// Square
class Square : public Geometry {
public:
    float size {1};

    // virtual wrapper functions call template function for square
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for squares
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(size * size);
    }
};

//---------------------------------------------
// Circle
class Circle : public Geometry  {
public:
    float radius {1};

    // virtual wrapper functions call template function for circle
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for Circles
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(radius * radius * 3.1415926535897932385L);
    }
};


//---------------------------------------------
// Main
int main()
{
    // get area of square using template based function T=float
    std::unique_ptr<Geometry> geometry = std::make_unique<Square>();
    float areaSquare;
    geometry->getArea(areaSquare);

    // get area of circle using template based function T=long double
    geometry = std::make_unique<Circle>();
    long double areaCircle;
    geometry->getArea(areaCircle);

    std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl;
    return 0;
}

Выход:

Площадь квадрата 1, площадь круга 3.1415926535897932385

Попробуйте здесь

...