不同类型的模板类列表

时间:2014-08-03 18:33:07

标签: c++ templates

我试图列出变量类型的模板类。因此,想法是循环所有具有共同功能的对象列表,例如, getValue,但是不同的类型。类型可以是任何类型,原始类型或对象。

我需要这个,因为我希望有一个类,它有一个我希望能够在运行时构建的不同类型的属性列表。

所以我的课看起来像是:

class MyClass {
    std::list<Attribute<?>*> attributes;
};

我的属性模板:

template<typename T>
class Attribute {
public:
    Test(const T &t) : _t(t) {}

    T getValue() const { return _t; }
    void setValue(const T &t) { _t = t; }

private:
    T _t;
};

int main() {
    MyClass myClass;
    myClass.attributes.push_back(new Attribute<int>(42));
    myClass.attributes.push_back(new Attribute<double>(42.0));
}

正如你可以看到我放的MyClass列表?因为那是我的问题。我不知道如何制作一个列表,它将采用不同类型的属性模板,即int,double等。

std::list<Attribute<?> *> attributes;

在Java中,可以使用泛型。在C ++中是否有可能用某种构造来做到这一点?我尝试使用可变参数模板,但这似乎无助于解决我的问题。

我需要这个但不是Java,用C ++:

public class GenericAttribute<T> {

    private T value;

    public GenericAttribute (T value) {
        setValue(value);
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

public static void main(String[] args) {

    class Custom {
        public Custom() {}

        @Override public String toString() {
            return "My custom object";
        }
    }

    List<GenericAttribute<?>> attributes = new ArrayList<GenericAttribute<?>>();
    attributes.add(new GenericAttribute<Integer>(1));
    attributes.add(new GenericAttribute<Double>(3.1415926535));
    attributes.add(new GenericAttribute<Custom>(new Custom()));

    for (GenericAttribute<?> attr : attributes) {
        System.out.println(attr.getValue());
    }
}

输出:

1
3.1415926535
My custom object

感谢您的帮助!

6 个答案:

答案 0 :(得分:4)

版本3:非常高级(不要在家尝试:D)

class Attribute {
private:
    struct Head {
        virtual ~Head() {}
        virtual void *copy() = 0;
        const type_info& type;
        Head(const type_info& type): type(type) {}
        void *data() { return this + 1; }
    };
    template <class T> struct THead: public Head {
        THead(): Head(typeid(T)) {}
        virtual ~THead() override { ((T*)data())->~T(); }
        virtual void *copy() override {
            return new(new(malloc(sizeof(Head) + sizeof(T)))
                THead() + 1) T(*(const T*)data()); }
    };
    void *data;
    Head *head() const { return (Head*)data - 1; }
    void *copy() const { return data ? head()->copy() : nullptr; }
public:
    Attribute(): data(nullptr) {}
    Attribute(const Attribute& src): data(src.copy()) {}
    Attribute(Attribute&& src): data(src.data) { src.data = nullptr; }
    template <class T> Attribute(const T& src): data(
      new(new(malloc(sizeof(Head) + sizeof(T))) THead<T>() + 1) T(src)) {}
    ~Attribute() {
        if(!data) return;
        Head* head = this->head();
        head->~Head(); free(head); }
    bool empty() const {
        return data == nullptr; }
    const type_info& type() const {
        assert(data);
        return ((Head*)data - 1)->type; }
    template <class T>
      T& value() {
        if (!data || type() != typeid(T))
            throw bad_cast();
        return *(T*)data; }
    template <class T>
      const T& value() const {
        if (!data || type() != typeid(T))
            throw bad_cast();
        return *(T*)data; }
    template <class T>
      void setValue(const T& it) {
        if(!data)
            data = new(new(malloc(sizeof(Head) + sizeof(T)))
                THead<T>() + 1) T(it);
        else {
            if (type() != typeid(T)) throw bad_cast();
            *(T*)data = it; }}
public:
    static void test_me() {
        vector<Attribute> list;
        list.push_back(Attribute(1));
        list.push_back(3.14);
        list.push_back(string("hello world"));
        list[1].value<double>() = 3.141592;
        list.push_back(Attribute());
        list[3].setValue(1.23f);
        for (auto& a : list) {
            cout << "type = " << a.type().name()
              << " value = ";
            if(a.type() == typeid(int)) cout << a.value<int>();
            else if (a.type() == typeid(double)) cout << a.value<double>();
            else if (a.type() == typeid(string)) cout << a.value<string>();
            else if (a.type() == typeid(float))  cout << a.value<float>();
            cout << endl;
        }
    }
};

输出:

type = i value = 1
type = d value = 3.14159
type = Ss value = hello world
type = f value = 1.23

<强>解释

Attribute包含data指针,由 strange placement new初始化:new(new(malloc(sizeof(Head) + sizeof(T))) THead<T>() + 1) T(src)首先为{{1}分配足够的空间(应该是Head,应该适用于任何架构的任何对齐)和类型本身,构造2*sizeof(void*) (初始化指向虚方法表的指针)并键入info)并在head =我们想要数据的地方之后移动指针。然后使用复制构造函数(或移动构造函数) THead<T>()通过另一个展示位置构建对象。 T(src)有两个虚函数 - 析构函数和struct Head,它在copy()中实现,并在THead<T>复制构造函数中使用。最后Attribute(const Attribute&)析构函数调用~Attribute()虚拟析构函数并释放内存(如果数据!= nullptr)

版本1:简单属性列表

~Head()

输出:

#include <vector>
#include <typeinfo>
#include <iostream>
#include <cstdlib>
#include <new>

using namespace std;

class Attributes {
public:
    typedef pair<const type_info&,void*> value_type;
    typedef vector<value_type> vect;
    typedef vect::const_iterator const_iterator;
    template <class T>
      void add(const T& value) {
        data.push_back(pair<const type_info&,void*>(
          typeid(T), new(malloc(sizeof(T))) T(value))); }
    const_iterator begin() const {
        return data.begin(); }
    const_iterator end() const {
        return data.end(); }
private:
    vect data;
} attrs;

int main() {
    attrs.add(1);
    attrs.add(3.14);
    for (auto a : attrs) {
        cout << a.first.name() << " = ";
        if(a.first == typeid(int))
            cout << *(int*)a.second;
        else if(a.first == typeid(double))
            cout << *(double*)a.second;
        cout << endl;
    }
}

版本2(命名属性):

i = 1
d = 3.14

答案 1 :(得分:1)

看看variant - 这是一个可以是多种不同类型之一的类,但是在你需要对值进行操作之前你不介意哪个类,在这种情况下你可以使用访问者模式访问所有类型。

它实际上是C'union'构造的C ++类型感知版本,但是因为它'知道'设置了哪种类型,它可以提供类型安全。

变体的最大问题是,如果您公开您的实现并允许任何客户端将几乎任何类型放入您的变体(attributes.push_back(new Attribute<Widget>(myWidget));)中,那么您将无法对其进行任何操作。例如。如果你想对你的属性中的所有值进行“求和”,你需要将它们转换为数字表示,而Widget可能不是。

更大的问题是,一旦你将这些项目作为属性捕获,你试图用它们做什么?通过它们枚举调用getValue()将根据您输入的类型给出不同的结果。访问者对象可以工作,但仍然不清楚这将带来什么价值。

可能是您需要不同的东西,例如界面,例如IAttribute抽象基础类型,只要它符合具有getValueAsDouble()方法或getValueAsString()方法的接口,您可以对任何传入的类型执行此操作 - 在这种情况下不需要变体或访问者。 / p>

答案 2 :(得分:1)

由于Attribute<int>的类型与Attribute<double>不同,因此如果不创建公共基本类型,则无法使用列表或向量。

或者,您可以将不同类型存储到std::tuple

以下可能会有所帮助:

template <typename ... Ts>
class MyClass {
public:
    MyClass(const Ts&... args) : attributes(args...) {}
private:
    std::tuple<Attribute<Ts>...> attributes;
};

int main()
{
    MyClass<int, double> myClass(42, 42.0);
    return 0;
}

答案 3 :(得分:0)

正如您已经指出的那样,在java中这样做要容易得多,因为所有类都扩展了java.lang.Object。在C(++)中,有一种类似的方法,但只有指针 - 你可以使用void*。您的列表将如下所示:

std::list<Attribute<void*> *> attributes;

当然,void*不保存类型。如果需要,请将此字段添加到Attribute - 类:

public:
    std::type_info type;

然后,如果您创建Attributre的实例,请执行此操作(可能来自构造函数):

type = typeinfo(type_to_store);

对于corse,如果你从构造函数中执行此操作,则需要在调用构造函数的代码中运行typeinfo

然后,您可以从该字段获取该类的名称,并从void*返回该实例:

std::string name = attribute->type_info.name();
void * instance  = attribute->getValue();

答案 4 :(得分:0)

您想对此系列执行哪些操作?您是否想要在所有getValue个实例上调用Attribute<int>而忽略其他实例?在这种情况下,基类很好 - 你只是不要使getValue成员函数virtual(因为它的类型取决于子类)并使用RTTI在运行时恢复类型信息: / p>

struct AnyAttribute {
  // Needs at least one virtual function to support dynamic_cast.
  virtual ~AnyAttribute() {}
};

template<typename T>
struct Attribute : AnyAttribute { … };

int main() {
  std::vector<AnyAttribute*> attributes;
  attributes.push_back(new Attribute<int>(13));
  attributes.push_back(new Attribute<int>(42));
  attributes.push_back(new Attribute<double>(2.5));

  for (const auto attribute : attributes) {
    if (const auto int_attribute = dynamic_cast<Attribute<int>>(attribute)) {
      std::cout << int_attribute->getValue() << '\n';
    }
  }
}

答案 5 :(得分:0)

一个明显但天真的解决方案是:

  • 从基地Attribute<T>继承AttributeBase
  • 在容器中存储AttributeBase(智能)指针(向下转发)
  • 在读取容器元素时,以某种方式计算其类型(RTTI
  • 转回派生Attribute<T>(向上翻译)

你可以通过添加另一个间接层来美化这个丑陋的解决方案:制作一个通用容器,为每个属性存储通用容器,这样就可以在幕后进行转换。

您可以使用集成到RTTI语言的功能,例如type_info但据我所知,它的可靠性值得怀疑。更好的解决方案是将每种Attribute<T>类的某种静态唯一ID包装起来,并为AttributeBase添加一个访问器来检索它。您可以向相关的typedef添加Attribute<T>到您的唯一ID类,以便更轻松地进行投射。

到目前为止一切顺利,但在现代C ++中,我们知道,当您需要RTTI时,它可能意味着您的整体代码设计出现了问题。

我不知道你有什么确切的任务,但是我的直觉感受&#34;告诉我你可能会使用multiple dispatchdouble dispatch)消除对RTTI的需求,比如代码中的Visitor pattern(他们总是在看到RTTI时说)。

另外,检查一些其他技巧的灵感: More C++ Idioms/Coercion by Member Template