如何创建一个模板类对象的数组?

12

我有一段时间没有做C++编程了,决定利用业余时间研究一下。我想写一个小型的数据库程序,但在创建一个模板类对象数组时遇到了问题。

我想使用这个类来表示数据库记录中的一个字段。

template <class T, int fieldTypeId>
class Field
{
private:
    T field;
    int field_type;
public:
    // ...
};

我希望使用该类的数组来表示数据库中的记录。

class Database_Record
{
private:
    int id;
    Field record[];
public:
    Database_Record(int);
    Database_Record(int, Field[]);
   ~Database_Record();
};

我卡住的地方是在Database_Record类中创建数组,因为那是一个模板类对象的数组,每个元素可能是不同类型的,我不确定因为这个我需要如何声明数组。我尝试的事情是否可能或者我走错了路?谢谢您的帮助。

不行。数组的所有元素都是相同类型的。Field<Type,0>与Field<Type,1>或Field<Othertype,0>是不同的类型。你不能把它们放在一个数组中。 - Cubic
数组的元素必须具有相同的类型。类模板的不同实例化是不同的类型。要么数组只能容纳一个版本,要么它们都可以继承自某个基类,并且您可以拥有一个基类指针的数组? - BoBTFish
Field 不是一个“模板类”。它是一个类模板。这种区别是你困惑的唯一根源。Field<int> 将会是一个模板类。 - Kerrek SB
而且,您始终可以将指向对象的指针存储在整数数组中。我认为这是最简单的方法。祝您有美好的一天。 - TomeeNS
8个回答

20

Field<T1>Field<T2> 是两种完全不同的类型。如果要将它们放入一个向量中处理,则需要将它们泛化。您可以编写 AbstractField

struct AbstractField{
  virtual ~AbstractField() = 0;
};

template<class T,int fieldTypeId>
class Field: public AbstractField{
  private:
    T field;
  public:
    const static int field_type;
  public:
    virtual ~Field(){}
};

class Database_Record{
  std::vector<AbstractField*> record; 
  public:
    ~Database_Record(){
      //delete all AbstractFields in vector
    }
};

然后保持一个AbstractFieldvector。同时使用vector代替[]。在AbstractField中使用AbstractField*,并至少编写一个纯虚函数。

您可以将AbstractField的析构函数设置为纯虚函数。不要忘记在~Database_Record()中删除所有AbstractField


2
谢谢!你的回答让我觉得现在我走在了正确的方向上。 - ChadNC
@NeelBasu 谢谢你的回答。对我来说非常有启发性。但有一点不太清楚。请问为什么在 AbstractField 中需要有一个纯虚方法? - Steve
有人能解释一下如何读取字段吗?您需要在AbstractField中为其提供虚函数,但返回类型必须是模板。 - help
@help 也许您不需要这么做。您可以使用 dynamic_cast 并调用派生类的适当 getter 函数。 - Neel Basu

1

你走错了方向。

模板用于创建不同类型:std::vector<int>std::vector<float>的区别与intfloat的区别相同(并且一样多)。

你的语法也有误;要创建动态数组,你需要在Database_Record中放入以下成员:

 std::vector<Field> record; // if this was possible; however, it's not

为了将几个不同类型的对象放入单个数组中,它们应该具有一个共同的基类。

1
为了创建不同类型的数组,您需要一个对象的基类,该数组将是指向该基类的指针数组。例如,
class Field
{
public:
    virtual ~Field() {}
    virtual std::string toString() const = 0;
    // and possibly other interface functions...
};

template <class T> FieldImpl : public Field
{
public:
    virtual std::string toString() const
    {
        std::stringstream ss;
        ss << val;
        return ss.str();
    }

    // implementation of possibly other interface functions        

private:
    T val;
}

这将是您所需的类型。数组将类似于

std::vector<std::unique_ptr<Field>> my_array;

然后,您可以使用接口函数对数组执行操作,例如:

my_array[i]->toString();

可以使用std::shared_ptr。但它应该是一种智能指针,用于自动内存管理。否则下一个内存泄漏就在门口了。 - Ralph Tandetzky

1

正如之前所说,C++模板不是这样工作的。

同时,由于性能限制,使用继承和指针向量不适合实现数据库记录。

退一步看问题,以更抽象的方式来看待问题。从您的代码中我理解到,意图是将不同类型的任意数量的字段打包成连续的内存块。示意图如下:

struct DBRecord {
    Type1 f1;
    Type2 f2;
    Type3 f3;
    Type4 f4;
    // etc...
}

您可以通过一个有点丑陋但实用的结构来实现这一点,该结构由抽象模板声明和几个特化组成。

声明如下:

template <
    typename T1,
    typename T2 = void,
    typename T3 = void,
    typename T4 = void,
    typename T5 = void,
    typename T6 = void,
    typename T7 = void,
    typename T8 = void,
    typename T9 = void,
    typename T10 = void
> struct DBRecord;

它显然限制了字段的最大数量。如果您需要真正任意数量的字段,则需要切换到列导向的范例。

然后,部分特化应该为每个参数数量从1到10声明结构的解剖学:

template <
    typename T1
> struct DBRecord <T1, void, void, void, void, void, void, void, void, void> 
{
    int id;
    T1 f1;
    DBRecord(int ID, T1 F1) {/*...*/};
};

template <
    typename T1,
    typename T2
> struct DBRecord <T1, T2, void, void, void, void, void, void, void, void> 
{
    int id;
    T1 f1;
    T2 f2;
    DBRecord(int ID, T1 F1, T2 F2) {/*...*/};
};

// etc...

现在,如果您想要,可以将表分配为特定类型的记录数组,在一个new[]调用中。 而且,通常您不需要关心每个字段的销毁,因为您会释放整个结构的内存。
宏可以帮助使这些专业化声明更加紧凑。

正如我所说的,“如果你需要一个真正任意数量的字段,你需要切换到列导向的范式”。但是这样一来,问题就完全不同了。我的答案阐述了在C++中将字段打包到结构体中的一种机器友好的方式。 - Krit

0
创建了两个示例类,用于快速调试报告,灵感来自C#的ToString()重写:
class UnknownType_t {
public:
    virtual operator long&() { throw "Unsupported"; };
    virtual operator const std::string() { throw "Unsupported"; };
    virtual void Set(char*, long) = 0;
};

class Number : public UnknownType_t {
public:
    Number(long _n) { n = _n; };
    virtual operator long&() { return n; };
    virtual void Set(char* buf, long size) {
        n = 0;
        memcpy(&n, buf, size);
    }

    long n;
};

class String : public UnknownType_t {
public:
    String(const char *_s) : s(_s) {};
    virtual operator const std::string() { return s; };
    virtual void Set(char* buf, long size) {
        s = std::string(reinterpret_cast<char*>(buf), size);
    }

    std::string s;
};

您可以尝试使用 dynamic_cast 检查类型,结果在 UnknownType_t 的常规数组中看起来像是{n=123}或{s="ABC"}。

Base 不是纯虚的,这是有意为之 - 跨越 getter 将没有意义...


0

将每个使用不同模板参数的实例化视为不同的类。您需要存储特定的类(即Field<int,17>),或者您需要使Field具有非模板基类,以便您可以将其存储在列表中。


0
你正在错误地使用模板。用不同类型实例化类模板将再次产生可能具有不同大小的两种不同类型,这使得无法将它们存储在数组中。
如果您想统一处理不同类型,请使用继承。并且当使用继承时,请勿使用普通数组,而是使用vectorstd::array
您的代码中还有一堆奇怪的东西:为什么要存储一个已经在静态上下文中知道的fieldTypeId?我猜这与您作为模板参数使用的类型T有关。通过部分特化外部化机制:
template<typename T>
struct fieldTypeId;

template<>
struct fieldTypeId<int> {
  const static int value = 0;
}; 
// etc....

如果我完全错了,而你确实知道你在做什么:可以通过一些任意类型(例如Boost.Any)使用类型擦除。

0
你可以像这样做 -
template <class T, int fieldTypeId>
class Field
{
private:
    T field;
    int field_Type;
};

template <class T, int fieldTypeId>
class Database_record
{
private:
    int id;
    std::vector<Field<T, fieldTypeId> > record_;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接