Я слышал, что шаблоны функций-членов класса C ++ не могут быть виртуальными. Это правда?
Если они могут быть виртуальными, то каков пример сценария, в котором можно использовать такую функцию?
Я слышал, что шаблоны функций-членов класса C ++ не могут быть виртуальными. Это правда?
Если они могут быть виртуальными, то каков пример сценария, в котором можно использовать такую функцию?
Ответы:
Шаблоны - это все о генерации кода компилятором во время компиляции . Виртуальные функции - это система времени выполнения, которая определяет, какую функцию вызывать во время выполнения .
Как только система времени выполнения выяснила, что ей нужно будет вызвать виртуализированную виртуальную функцию, компиляция завершена, и компилятор больше не может генерировать соответствующий экземпляр. Поэтому у вас не может быть шаблонов виртуальных функций-членов.
Тем не менее, есть несколько мощных и интересных методов, вытекающих из сочетания полиморфизма и шаблонов, в частности, так называемое стирание типов .
Virtual functions are all about the run-time system figuring out which function to call at run-time
- извините, но это довольно неправильный путь и довольно запутанный. Это просто косвенное обращение, и в нем не задействовано «выяснение времени выполнения», во время компиляции известно, что вызываемая функция - это та, на которую указывает n-й указатель в vtable. «Выяснение» подразумевает, что есть проверки типа и тому подобное, что не так. Once the run-time system figured out it would need to call a templatized virtual function
- является ли функция виртуальной, известно во время компиляции.
void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }
, то он «знает», какая функция вызывается в вызываемой точке cb.f()
, и не знает, для чего vb.f()
. Последнее должно быть обнаружено во время выполнения , исполняющей системой . Если вы хотите назвать это «выяснением», и является ли это более или менее эффективным, это немного не меняет эти факты.
Из шаблонов C ++ Полное руководство:
Шаблоны функций-членов не могут быть объявлены виртуальными. Это ограничение наложено, потому что обычная реализация механизма вызова виртуальной функции использует таблицу фиксированного размера с одной записью на виртуальную функцию. Однако количество экземпляров шаблона функции-члена не фиксируется до тех пор, пока не будет переведена вся программа. Следовательно, поддержка шаблонов виртуальных функций-членов потребует поддержки совершенно нового механизма в компиляторах и компоновщиках C ++. Напротив, обычные члены шаблонов классов могут быть виртуальными, поскольку их число фиксируется при создании экземпляра класса.
C ++ не разрешает виртуальные функции-члены шаблона прямо сейчас. Наиболее вероятной причиной является сложность его реализации. Раджендра приводит веские причины, почему это нельзя сделать прямо сейчас, но это может быть возможно при разумных изменениях стандарта. Особенно трудно определить, сколько экземпляров шаблонной функции действительно существует, и создание виртуальной таблицы кажется трудным, если учесть место вызова виртуальной функции. У людей, занимающихся стандартизацией, сейчас есть много других дел, и C ++ 1x - большая работа и для авторов компиляторов.
Когда вам понадобится шаблонная функция-член? Однажды я столкнулся с такой ситуацией, когда я пытался реорганизовать иерархию с чисто виртуальным базовым классом. Это был плохой стиль для реализации различных стратегий. Я хотел изменить аргумент одной из виртуальных функций на числовой тип и вместо перегрузки функции-члена и переопределить каждую перегрузку во всех подклассах, я пытался использовать функции виртуального шаблона (и должен был выяснить, их не существует). .)
Давайте начнем с некоторой предыстории виртуальных таблиц функций и того, как они работают ( источник ):
[20.3] В чем разница между вызовом виртуальных и не виртуальных функций-членов?
Не виртуальные функции-члены разрешаются статически. То есть функция-член выбирается статически (во время компиляции) в зависимости от типа указателя (или ссылки) на объект.
Напротив, виртуальные функции-члены разрешаются динамически (во время выполнения). То есть функция-член выбирается динамически (во время выполнения) в зависимости от типа объекта, а не от типа указателя / ссылки на этот объект. Это называется «динамическое связывание». Большинство компиляторов используют какой-либо вариант следующего метода: если объект имеет одну или несколько виртуальных функций, компилятор помещает в объект скрытый указатель, называемый «виртуальный указатель» или «v-указатель». Этот v-указатель указывает на глобальную таблицу, называемую «виртуальная таблица» или «v-таблица».
Компилятор создает v-таблицу для каждого класса, который имеет хотя бы одну виртуальную функцию. Например, если у класса Circle есть виртуальные функции для draw () и move () и resize (), то будет точно одна v-таблица, связанная с классом Circle, даже если бы существовал gazillion объектов Circle, и указатель v каждый из этих объектов Circle будет указывать на v-таблицу Circle. В самой 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 не объявлен в базовом классе.
Вот еще один ответ переполнения стека с обходным путем : нужен обходной путь члена виртуального шаблона .
Следующий код можно скомпилировать и запустить правильно, используя 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&)'
Итак, очевидно, что:
Нет, они не могут. Но:
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);
};
имеет тот же эффект, если все, что вы хотите сделать, это иметь общий интерфейс и отложить реализацию до подклассов.
Foo
указатель квалифицирован как Foo<Bar>
, он не может указывать на Foo<Barf>
или Foo<XXX>
.
Нет, функции-члены шаблона не могут быть виртуальными.
В других ответах предложенная функция шаблона представляет собой фасад и не дает никакой практической выгоды.
Язык не допускает функции виртуальных шаблонов, но с обходным путем можно иметь и то и другое, например, одну реализацию шаблона для каждого класса и виртуальный общий интерфейс.
Однако для каждой комбинации типов шаблонов необходимо определить фиктивную функцию виртуальной оболочки:
#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
Попробуй здесь
Чтобы ответить на вторую часть вопроса:
Если они могут быть виртуальными, то каков пример сценария, в котором можно использовать такую функцию?
Это не необоснованная вещь, чтобы хотеть сделать. Например, Java (где каждый метод является виртуальным) не имеет проблем с общими методами.
Одним из примеров использования C ++ шаблона виртуальной функции в C ++ является функция-член, которая принимает универсальный итератор. Или функция-член, которая принимает объект универсальной функции.
Решением этой проблемы является использование стирания типов с boost :: any_range и boost :: function, что позволит вам принять универсальный итератор или функтор без необходимости превращать вашу функцию в шаблон.
Существует обходной путь для «метода виртуального шаблона», если набор типов для метода шаблона известен заранее.
Чтобы показать идею, в приведенном ниже примере используются только два типа ( 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
могут быть обнародованы и использованы напрямую). Я добавил его так, чтобы он выглядел как настоящий «виртуальный» метод шаблона.
Base
класс каждый раз, когда вам нужно вызвать функцию шаблона с типом аргумента, несовместимым с теми, которые были реализованы до сих пор. Избежание этой необходимости - это намерение шаблонов ...
В то время как на старый вопрос, на который многие отвечали, я полагаю, лаконичный метод, не сильно отличающийся от других опубликованных, заключается в использовании второстепенного макроса для облегчения дублирования объявлений классов.
// 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
};
Преимущество здесь в том, что при добавлении нового поддерживаемого типа все это можно сделать из абстрактного заголовка и, возможно, отказаться от него в нескольких файлах исходного кода / заголовка.
По крайней мере, в 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
Попробуй это:
Напишите в classeder.h:
template <typename T>
class Example{
public:
T c_value;
Example(){}
T Set(T variable)
{
return variable;
}
virtual Example VirtualFunc(Example paraM)
{
return paraM.Set(c_value);
}
Проверьте, если вы работаете с этим, чтобы написать этот код в main.cpp:
#include <iostream>
#include <classeder.h>
int main()
{
Example exmpl;
exmpl.c_value = "Hello, world!";
std::cout << exmpl.VirtualFunc(exmpl);
return 0;
}