如何将项目放入具有模板化超类类型的向量中



假设我有以下 2 个类SuperDeriv其中DerivSuper的子类。

SuperDeriv都是模板化类。我想创建一个类型Super<?>vector。其中?表示任何类型。目前我已经想出了这个:

#include <iostream>
#include <vector>
template <typename T>
class Super {
public:
T val;
Super(T val) : val(val) {};
};
template <typename T>
class Deriv : public Super<T> {
public:
Deriv(T val) : Super<T>(val) {};
};
int main() {
std::vector<Super*> a;
a.push_back(new Deriv<int>(1));
a.push_back(new Deriv<float>(1.0f));
std::cout << a[0]->val << std::endl;
return 0;
}

当然,这是行不通的,因为std::vector<Super*>需要一个像std::vector<Super<int>*>这样的模板类型。但是,这样做的问题是我只能将项目添加到Super<int>*类型的向量中,而不能Super<float>*.

如何更改此代码以允许我将Super类型或其派生添加到具有任何模板类型(如intfloatshort等(的向量中?

不确定这是否可以解决您的问题,但它可能会给您一些想法。这里的基本思想是为各种T制作一个超级类。对于基本数据类型,需要包装类。

#include <iostream>
#include <cstdio>
#include <vector>
#include <string>
class SuperT {
public:
virtual std::string AccessData() = 0;
};
class IntWraper : public SuperT {
public:
IntWraper(int i) : val(i) { };
virtual std::string AccessData() { return std::to_string(val); };
private:
int val;
};
class FloatWraper : public SuperT {
public:
FloatWraper(float f) : val(f) { };
virtual std::string AccessData() { return std::to_string(val); };
private:
float val;
};

class RealSuper {
public:
virtual std::string DoSomething() = 0;
};
template <typename T>
class Super : public RealSuper {
public:
T* wraper_val_;
Super(T* w_val) : wraper_val_(w_val) { };
~Super() { if(wraper_val_) delete wraper_val_; };
virtual std::string DoSomething() { return wraper_val_->AccessData(); }
};
template <typename T>
class Deriv : public Super<T> {
public:
Deriv(T* w_val) : Super<T>(w_val) {};
};
int main() {
std::vector<RealSuper*> a;
a.push_back(new Deriv<IntWraper>(new IntWraper(1)));
a.push_back(new Deriv<FloatWraper>(new FloatWraper(1.0f)));
std::cout << a[0]->DoSomething() << std::endl;
std::cout << a[1]->DoSomething() << std::endl;
return 0;
}

std::vector<Super*> a;

错了。

代码中没有名为Super的类型。是的,您声明了名为"Super"的东西,但它不是一个类,它是一个类模板。

该功能的名称说明了一切。这是一个模板。编译器将使用Super在编译时生成新类型。

例如,Super<int>引用编译器在填充Super模板中的空洞T时生成的类。

那么为什么a[0]->val不可能工作呢?好吧,想象一下:

template<>
struct Super<std::string> {
std::string my_val;
};

我们专门Super所以当用std::string实例化时,它不再有val成员,而是my_val成员。

现在,您希望此代码做什么?

std::vector<Super*> a;
a.push_back(new Deriv<std::string>);
std::cout << a[0]->val << std::endl;

很令人费解不是吗?您需要在运行时出现编译错误。由于变量的存在(或不存在(是在编译时确定的,因此这是不可能的。


现在我们如何解决您的问题?

在您的情况下,就像在Super上方添加一个接口一样简单,并公开实现执行计算所需行为的函数:

struct Interface {
void print(std::ostream) const = 0;
bool lessThan(double) const = 0;
};
template <typename T>
struct Super : Interface {
T val;
Super(T val_) : val{val_} {};
// We implement the needed behavior.
void print(std::ostream o) const override {
o << val << std::endl;
}
// Example of calculation
bool lessThan(double rhs) const override {
return val < rhs;
}
};

现在您可以执行以下操作:

std::vector<Interface*> a;
// ...
a[0]->print(std::cout);
a[0]->lessThan(3.7);

创建一个缓存类型的新类RealSuper是一种可能的解决方法。

它并不完美,但恐怕没有比这更好的了:-

class RealSuper{                                //<-- new class
public: enum TYPE{ type_int, type_float, type_notIni };  // (yes, it is awkward)
TYPE typee = type_notIni;
};
template <typename T>   class Super : public RealSuper { //<-- modify
public:  T val;
Super(T val) : val(val) {
if( std::is_same<T, int>::value ){
typee = type_int;
}else if( std::is_same<T, float>::value ){
typee = type_float;
}
};
};
template <typename T>  class Deriv : public Super<T> {
public: Deriv(T val) : Super<T>(val) {};
};
int main() {
std::vector<RealSuper*> a;
a.push_back(new Deriv<int>(1));
a.push_back(new Deriv<float>(1.0f));
for(auto ele: a){
switch( ele->typee ){
case RealSuper::TYPE::type_int: {
int value=static_cast<Super<int>*>(ele)->val;
std::cout << value << std::endl; 
};break;
case RealSuper::TYPE::type_float :{
float value=static_cast<Super<float>*>(ele)->val;
std::cout << value << std::endl; 
};break;
}
}
return 0;
}

现场演示

这是另一个演示,展示了使用虚拟函数的方法。

C++没有您要求的功能(我相信这是一种化(。 这意味着您不能在位置 1 中存储任意类型,然后在位置 2 中完全不相关的源文件中指定任意操作,然后将操作应用于位置 3 中的数据。

您可以解决许多接近您要求的问题:限制要在位置二执行的操作,限制在位置一存储的类型,问题就可以解决。 或者,限制您在位置 3 支持的类型 x 操作对。

请注意,受限制的操作集的组合也可以工作。

理论上,您可以在C++二进制文件中嵌入完整的编译器或解释器,编译代码并动态加载它。 (这基本上就是 C#/Java 管理实现的方式(。 对于大多数问题来说,这是相当不切实际的。 该语言对此没有提供任何支持,但这C++,您可以做任何事情(甚至编写Java/C#编译器(。

由于没有关于您需要解决的潜在问题的信息,我无法告诉您以上哪种是正确的方法。

这就是为什么"如果我有X,我能解决Y,但我无法弄清楚X。 我知道,我只会问堆栈溢出如何做X!"被称为X/Y问题。 我们可能可以求解 Y,但你问的是 X,它甚至没有描述 Y。 随意发布 Y 问题并询问它。 使用上面的 [提问] 按钮。


限制在存储中处理的类型:

存储std::variant. 使用std::visit. 或者自己写或使用boost::variant.


限制要执行的操作:

使用类型擦除在存储类型时生成按类型操作。


在调用点限制操作类型对:

使用RTTI 来减去存储的类型,有一个大型交换机交换机,然后使用解决方案一。

最新更新