问题描述
问题
我想要一个指向模板类实例的指针数组。如果C ++允许在基类中使用模板化的派生类进行模板化的虚拟方法,则将解决我的问题。
因此,如何实现模板化虚拟方法?
约束
template参数是无限可变的,例如,我无法枚举此模板类的每个特殊化。模板类T
可以是任何POD,POD数组或POD的结构。
T
的完整集合在编译时是已知的。基本上,我有一个文件,该文件定义了用于实例化对象的所有不同T
,并使用Xmacros(https://en.wikipedia.org/wiki/X_Macro)创建对象数组。
我知道这不是一个好主意。让我们暂时忽略一下。最终变得更加好奇。
可能的解决方案
这些是我研究过的东西。
创建基类和派生类
class Base {
virtual void SomeMethod() = 0;
}
template <class T>
class Derived : Base {
void SomeMethod() {...}
}
问题是我无法声明Base
中要重载的所有虚拟方法,因为无法对虚拟方法进行模板化。否则,这将是一个完美的解决方案。
std :: any / std :: variant
我正在使用C ++ 17,因此可以使用std::any
定义虚拟基本方法。但是它不能保存数组,因此不能在这里使用。
CRTP
看来这无助于我创建这些不同对象的数组。我需要做类似的事情
template <typename D,typename T>
class Base
{
...
};
template <typename T>
class Derived : public Base<Derived,T>
{
...
};
所以我仍然最终尝试创建Derived<T>
对象的数组。
访客模式
再次看来,我需要枚举Visitable
类需要服务的每种可能的类型,尽管并非不可能(再次,我有一个文件定义了所有不同的T
似乎更像Xmacros,这只会使问题变得更加复杂。
我的解决方案
这是我想出的。它将在https://www.onlinegdb.com/online_c++_compiler
中运行#include <iostream>
#include <array>
#include <typeinfo>
// Base class which declares "overloaded" methods without implementation
class Base {
public:
template <class T>
void Set(T inval);
template <class T>
void Get(T* retval);
virtual void Print() = 0;
};
// Template class which implements the overloaded methods
template <class T>
class Derived : public Base {
public:
void Set(T inval) {
storage = inval;
}
void Get(T* retval) {
*retval = storage;
}
void Print() {
std::cout << "This variable is type " << typeid(T).name() <<
",value: " << storage << std::endl;
}
private:
T storage = {};
};
// Manually pointing base overloads to template methods
template <class T> void Base::Set(T inval) {
static_cast<Derived<T>*>(this)->Set(inval);
}
template <class T> void Base::Get(T* retval) {
std::cout << "CALLED THROUGH BASE!" << std::endl;
static_cast<Derived<T>*>(this)->Get(retval);
}
int main()
{
// Two new objects
Derived<int>* ptr_int = new Derived<int>();
Derived<double>* ptr_dbl = new Derived<double>();
// Base pointer array
std::array<Base*,2> ptr_arr;
ptr_arr[0] = ptr_int;
ptr_arr[1] = ptr_dbl;
// Load values into objects through calls to Base methods
ptr_arr[0]->Set(3);
ptr_arr[1]->Set(3.14);
// Call true virtual Print() method
for (auto& ptr : ptr_arr) ptr->Print();
// Read out the values
int var_int;
double var_dbl;
std::cout << "First calling Get() method through true pointer." << std::endl;
ptr_int->Get(&var_int);
ptr_dbl->Get(&var_dbl);
std::cout << "Direct values: " << var_int << "," << var_dbl << std::endl;
std::cout << "Now calling Get() method through base pointer." << std::endl;
ptr_arr[0]->Get(&var_int);
ptr_arr[1]->Get(&var_dbl);
std::cout << "Base values: " << var_int << "," << var_dbl << std::endl;
return 0;
}
运行此命令时,它表明在Base
上调用方法正确指向Derived
实现。
This variable is type i,value: 3
This variable is type d,value: 3.14
First calling Get() method through true pointer.
Direct values: 3,3.14
Now calling Get() method through base pointer.
CALLED THROUGH BASE!
CALLED THROUGH BASE!
Base values: 3,3.14
基本上,我是在手动创建虚拟方法指针。但是,由于我明确地这样做,因此允许我使用Base
中的模板方法,该模板方法指向Derived
中的方法。更容易出错,例如,对于每个模板方法,我需要两次键入方法名称,即,我可能会弄乱:
template <class T> void Base::BLAH_SOMETHING(T inval) {
static_cast<Derived<T>*>(this)->WHOOPS_WRONG_CALL(inval);
}
因此,这毕竟是一个可怕的主意吗?对我来说,这似乎可以达到我避免模板化虚拟方法局限性的目标。这真的有什么问题吗?我知道,可能有一些方法可以使所有不必要的代码结构化,我只是专注于这种特定的结构。
解决方法
它更容易出错,例如,对于每个模板方法,我需要两次键入方法名称
哦,这是您最不用担心的。想象一下,如果您将类型转换为错误的类型。
至少可以避免头痛,并使用dynamic_cast
:
class Base {
public:
virtual ~Base() = default;
template <class T>
void Set(T inval) {
dynamic_cast<Derived<T>&>(*this).Set(inval);
}
template <class T>
T Get() {
return dynamic_cast<Derived<T>&>(*this).Get();
}
};
template <class T>
class Derived : public Base {
public:
void Set(T inval) {
storage = inval;
}
T Get() {
return storage;
}
private:
T storage{};
};
除此之外,我同意这些评论,这可能不是解决您问题的正确方法。
,处理包含未知类型的子类的常规方法是将整个对象移至虚拟函数。因此,代替
superclass->get_value(&variable_of_unknown_type);
print(variable_of_unknown_type);
您写
superclass->print_value();
现在,您无需了解子类可能包含的任何类型。
但这并不总是适当的,因为可能会有很多操作。如果您一直都在增加新的操作,那么使每个操作成为虚拟函数会很麻烦。另一方面,可能的子类的集合通常受到限制。在这种情况下,最好的选择是Visitor。可以这么说,访客将继承层次结构旋转90°。您无需修复操作集并自由添加新的子类,而是可以修复子集集并自由添加新的操作。所以代替
superclass->print_value();
您写
class PrinterVisitor : public MyVisitor
{
virtual void processSubclass1(Subclass1* s) { print(s->double_value); }
virtual void processSubclass2(Subclass2* s) { print(s->int_value); }
};
superclass->accept(PrinterVisitor());
现在accept
是基类中唯一的virtual
函数。请注意,没有强制转换可能在代码中的任何地方失败。