我听说C++类成员函数模板不能是虚函数。这是真的吗?
如果它们可以是虚函数,那么有什么情况下会使用这样的函数的例子呢?
如果它们可以是虚函数,那么有什么情况下会使用这样的函数的例子呢?
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
Base::Method
对于实际代码来说是多余的(VMethod
可以被公开并直接使用)。
我添加了它,以使其看起来像一个真正的“虚拟”模板方法。至少在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
我已经查看了所有14个答案,有些人给出了虚拟模板函数无法工作的原因,而其他人则展示了一种解决方法。甚至有一个答案表明虚拟类可以有虚拟函数,这并不令人惊讶。
我的答案将直接说明标准为什么不允许虚拟模板函数。因为很多人一直在抱怨。首先,我简直不敢相信有些人评论说虚拟函数可以在编译时推导出来。那是我听过的最愚蠢的事情。
无论如何,我确信标准规定对象的this指针是其成员函数的第一个参数。
struct MyClass
{
void myFunction();
}
// translate to
void myFunction(MyClass*);
C++ Primer
。例如,T* 可以转换为 const T*。数组可以转换为指针。然而,派生类作为模板参数不能转换为基类。struct A {};
struct B : A {};
template<class T>
void myFunction(T&);
template<>
void myFunction<A>(A&) {}
int main()
{
A a;
B b;
myFunction(a); //compiles perfectly
myFunction((A&)b); // compiles nicely
myFunction(b); //compiler error, use of undefined template function
}
所以我希望你能理解我的意思。你不能有一个虚拟模板函数,因为在编译器看来它们是两个完全不同的函数;因为它们的隐式 this 参数是不同类型的。
另一个原因为什么虚拟模板无法工作同样有效。由于虚拟表是实现快速虚拟函数的最佳方式。
this
参数是隐式的,为什么它应该成为一个模板参数呢?它可以被排除在整个混乱之外,然后解析的函数将像以前一样对其进行任何操作(不使用模板)。因此,对我来说,这与问题无关。 - Sz.我的当前解决方案是以下内容(已禁用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
);
}
}
我唯一不喜欢的是你必须要定义/注册所有的子类。
虚函数的调用方式是怎样的?
Vtable会为类的每个虚函数包含一个条目,在运行时它会选择特定函数的地址并调用相应的函数。
在使用函数模板时,如何调用正确的虚函数?
在使用函数模板时,用户可以使用任何类型来调用此函数。同一个函数基于不同的类型有多个版本。现在,在这种情况下,由于不同版本的存在,必须维护vtable中的许多条目以便调用同一个函数。