it-swarm.com.ru

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

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

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

261
WannaBeGeek

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

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

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

289
sbi

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

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

104
InQusitive

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

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

31
pmr

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

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

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

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

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

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


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

16
Mark Essel

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

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

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

11
Tom

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

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

4
dirkgently

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

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

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

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

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

3
exclipy

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

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

2
sad1raf

По крайней мере, в 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
0
Maxim Sinev

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

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

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

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

#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

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

0
andreaplanet