Может ли шаблон функции члена класса быть виртуальным?

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

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

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

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

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

Во время отправки виртуальной функции система времени выполнения следует объект v-указатель на класс v-table, затем следует соответствующий слот в таблице 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 не объявляется в базовом классе.


Здесь другой стек переполняет ответ с работой: нужен способ обхода виртуального шаблона.

Ответ 5

Следующий код можно скомпилировать и выполнить правильно, используя MinGW g++ 3.4.5 в окне 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.

Ответ 6

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

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);
};

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

Ответ 7

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

Ответ 8

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

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

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

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

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

Ответ 9

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

Чтобы показать эту идею, в приведенном ниже примере используются только два типа (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 можно публиковать и использовать напрямую). Я добавил его так, чтобы он выглядел как фактический метод "виртуального" шаблона.

Ответ 10

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

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

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

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

#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

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

Ответ 11

По крайней мере, виртуальные функции gcc 5.4 могут быть членами шаблона, но должны быть самими шаблонами.

#include <iostream>
#include <string>
class first {
protected:
    virtual std::string  a1() { return "a1"; }
    virtual std::string  mixt() { return a1(); }
};

class last {
protected:
    virtual std::string a2() { return "a2"; }
};

template<class T>  class mix: first , T {
    public:
    virtual std::string mixt() override;
};

template<class T> std::string mix<T>::mixt() {
   return a1()+" before "+T::a2();
}

class mix2: public mix<last>  {
    virtual std::string a1() override { return "mix"; }
};

int main() {
    std::cout << mix2().mixt();
    return 0;
}

Выходы

mix before a2
Process finished with exit code 0

Ответ 12

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

// abstract.h

// Simply define the types that each concrete class will use
#define IMPL_RENDER() \
    void render(int a, char *b) override { render_internal<char>(a, b); }   \
    void render(int a, short *b) override { render_internal<short>(a, b); } \
    // ...

class Renderable
{
public:
    // Then, once for each on the abstract
    virtual void render(int a, char *a) = 0;
    virtual void render(int a, short *b) = 0;
    // ...
};

Итак, теперь, чтобы реализовать наш подкласс:

class Box : public Renderable
{
public:
    IMPL_RENDER() // Builds the functions we want

private:
    template<typename T>
    void render_internal(int a, T *b); // One spot for our logic
};

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