我听说c++的类成员函数模板不能是虚的。这是真的吗?
如果它们可以是虚拟的,那么有什么场景可以使用这样的函数呢?
我听说c++的类成员函数模板不能是虚的。这是真的吗?
如果它们可以是虚拟的,那么有什么场景可以使用这样的函数呢?
当前回答
在虚函数的情况下如何调用正确的函数?
虚表将包含类的每个虚函数的条目,在运行时,它将选择特定函数的地址,并调用各自的函数。
如何正确的函数必须被调用在虚拟情况下连同函数模板?
在函数模板的情况下,用户可以使用任何类型调用该函数。这里相同的函数根据类型有几个版本。现在,在这种情况下,对于同一个函数,由于版本不同,必须维护vtable中的许多项。
其他回答
虽然很多人已经回答了一个老问题,但我相信一个简洁的方法,与其他发布的方法没有太大不同,就是使用一个小宏来帮助减轻类声明的重复。
// 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
};
这样做的好处是,当添加一个新支持的类型时,它可以从抽象头文件中完成,而不必在多个源文件/头文件中进行修改。
在其他答案中,建议的模板函数是一个门面,并不能提供任何实际的好处。
模板函数对于只编写一次代码很有用 不同的类型。 虚函数对于为不同的类提供公共接口非常有用。
该语言不允许虚拟模板函数,但通过一个变通方法,可以同时拥有两者,例如,每个类都有一个模板实现和一个虚拟公共接口。
但是,有必要为每个模板类型组合定义一个虚拟包装器函数:
#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
在这里试试
在虚函数的情况下如何调用正确的函数?
虚表将包含类的每个虚函数的条目,在运行时,它将选择特定函数的地址,并调用各自的函数。
如何正确的函数必须被调用在虚拟情况下连同函数模板?
在函数模板的情况下,用户可以使用任何类型调用该函数。这里相同的函数根据类型有几个版本。现在,在这种情况下,对于同一个函数,由于版本不同,必须维护vtable中的许多项。
不,他们不能。但是:
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);
};
如果您想要做的只是拥有一个公共接口并将实现推迟到子类,则效果大致相同。
我目前的解决方案如下(禁用RTTI -你也可以使用std::type_index):
#include <type_traits>
#include <iostream>
#include <tuple>
class Type
{
};
template<typename T>
class TypeImpl : public Type
{
};
template<typename T>
inline Type* typeOf() {
static Type* typePtr = new TypeImpl<T>();
return typePtr;
}
/* ------------- */
template<
typename Calling
, typename Result = void
, typename From
, typename Action
>
inline Result DoComplexDispatch(From* from, Action&& action);
template<typename Cls>
class ChildClasses
{
public:
using type = std::tuple<>;
};
template<typename... Childs>
class ChildClassesHelper
{
public:
using type = std::tuple<Childs...>;
};
//--------------------------
class A;
class B;
class C;
class D;
template<>
class ChildClasses<A> : public ChildClassesHelper<B, C, D> {};
template<>
class ChildClasses<B> : public ChildClassesHelper<C, D> {};
template<>
class ChildClasses<C> : public ChildClassesHelper<D> {};
//-------------------------------------------
class A
{
public:
virtual Type* GetType()
{
return typeOf<A>();
}
template<
typename T,
bool checkType = true
>
/*virtual*/void DoVirtualGeneric()
{
if constexpr (checkType)
{
return DoComplexDispatch<A>(this, [&](auto* other) -> decltype(auto)
{
return other->template DoVirtualGeneric<T, false>();
});
}
std::cout << "A";
}
};
class B : public A
{
public:
virtual Type* GetType()
{
return typeOf<B>();
}
template<
typename T,
bool checkType = true
>
/*virtual*/void DoVirtualGeneric() /*override*/
{
if constexpr (checkType)
{
return DoComplexDispatch<B>(this, [&](auto* other) -> decltype(auto)
{
other->template DoVirtualGeneric<T, false>();
});
}
std::cout << "B";
}
};
class C : public B
{
public:
virtual Type* GetType() {
return typeOf<C>();
}
template<
typename T,
bool checkType = true
>
/*virtual*/void DoVirtualGeneric() /*override*/
{
if constexpr (checkType)
{
return DoComplexDispatch<C>(this, [&](auto* other) -> decltype(auto)
{
other->template DoVirtualGeneric<T, false>();
});
}
std::cout << "C";
}
};
class D : public C
{
public:
virtual Type* GetType() {
return typeOf<D>();
}
};
int main()
{
A* a = new A();
a->DoVirtualGeneric<int>();
}
// --------------------------
template<typename Tuple>
class RestTuple {};
template<
template<typename...> typename Tuple,
typename First,
typename... Rest
>
class RestTuple<Tuple<First, Rest...>> {
public:
using type = Tuple<Rest...>;
};
// -------------
template<
typename CandidatesTuple
, typename Result
, typename From
, typename Action
>
inline constexpr Result DoComplexDispatchInternal(From* from, Action&& action, Type* fromType)
{
using FirstCandidate = std::tuple_element_t<0, CandidatesTuple>;
if constexpr (std::tuple_size_v<CandidatesTuple> == 1)
{
return action(static_cast<FirstCandidate*>(from));
}
else {
if (fromType == typeOf<FirstCandidate>())
{
return action(static_cast<FirstCandidate*>(from));
}
else {
return DoComplexDispatchInternal<typename RestTuple<CandidatesTuple>::type, Result>(
from, action, fromType
);
}
}
}
template<
typename Calling
, typename Result
, typename From
, typename Action
>
inline Result DoComplexDispatch(From* from, Action&& action)
{
using ChildsOfCalling = typename ChildClasses<Calling>::type;
if constexpr (std::tuple_size_v<ChildsOfCalling> == 0)
{
return action(static_cast<Calling*>(from));
}
else {
auto fromType = from->GetType();
using Candidates = decltype(std::tuple_cat(std::declval<std::tuple<Calling>>(), std::declval<ChildsOfCalling>()));
return DoComplexDispatchInternal<Candidates, Result>(
from, std::forward<Action>(action), fromType
);
}
}
我唯一不喜欢的是你必须定义/注册所有的子类。