使用模板实现的通用类型容器

时间:2014-09-29 16:22:50

标签: c++ templates containers

我有一个通常可以保存任何(基本)类型的类:

class Value
{
    private:
        int             i_value;
        unsigned int    ui_value;
        long            l_value;
        unsigned long   ul_value;
        short           s_value;
        float           f_value;
        double          d_value;
        char            c_value;
        bool            b_value;
        std::string     str_value;

        int type;

        void setValue(int value);
        void setValue(unsigned int value);
        void setValue(long value);
        void setValue(unsigned long value);
        void setValue(short value);
        void setValue(float value);
        void setValue(double value);
        void setValue(char value);
        void setValue(bool value);
        void setValue(std::string value);

    public:
        Value(int value);
        Value(unsigned int value);
        Value(long value);
        Value(unsigned long value);
        Value(short value);
        Value(float value);
        Value(double value);
        Value(char value);
        Value(bool value);
        Value(std::string value);

        Value(Value& other); //Copy Constructor
        ~Value();

        int getType();
        std::string toString(int format);

};

这很好,因为我可以做类似的事情:

Value * v1 = new Value(55);
Value * v2 = new Value(1.2);
Value * v3 = new Value("yes");
Value * v4 = new Value(true);

然而,正如你所看到的,它很丑陋;大量的超载使其成功。我在想模板可以使这个通用。但是,据我所知,你总是必须指明类型,这种类型会破坏班级的整个目的。

例如:

Value<int> * v1 = new Value<int>(55);
Value<double> * v2 = new Value<double>(1.2);
Value<string> * v3 = new Value<string>("yes");
Value<bool> * v4 = new Value<bool>(true);

如果我使用模板,我就不能像以前那样做vector<Value *>之类的事情了。这是正确的,还是我错过了某些可能有助于这种情况的模板方面?

3 个答案:

答案 0 :(得分:4)

您需要的只是模板一的父类:

class BaseValue
{
public:
    virtual ~BaseValue()
    {}
};

template<typename T>
class Value : public BaseValue
{
public:
    Value(const T& value)
        :m_value(value)
    {}
    void set(const T& value)
    {
        m_value = value; 
    }
    const T& get() 
    {
        return m_value; 
    }
    virtual ~Value()
    {}
private:
    T m_value;
};

std::vector<BaseValue*> values;
values.push_back(new Value<int>(1)); // int
values.push_back(new Value<double>(1.0)); // double
values.push_back(new Value<char*>("asdf")); // pointer to array on stack :(
values.push_back(new Value<char>('c')); // char

答案 1 :(得分:3)

第一个问题:使用模板工厂功能而不是新功能。

第二个问题:使用一个共同的基类。

为了能够正确删除向量上指针所指向的对象,您需要一个虚拟析构函数。另外,为了对指向基类的指针做任何有用的事情,你需要在基类中使用虚方法。

示例:

class ValueBase
{
public:
    virtual ~ValueBase() = default;
    virtual void Print(std::ostream & os) const = 0;
};

std::ostream & operator<< (std::ostream & os, const ValueBase & value)
{
    value.Print(os);
    return os;
}

template<typename T> class Value : public ValueBase
{
    T value;
public:
    Value(const T & v) : value(v) {}
    const T & Get() const;
    void Set(const T & v);
    void Print(std::ostream & os) const
    {
        os << value;
    }
    // ...
};

template<typename T> Value<T> * NewValue(const T & v)
{
    return new Value<T>(v);
}

现在你可以做到

ValueBase * v1 = NewValue(55);
ValueBase * v2 = NewValue(1.2);
ValueBase * v3 = NewValue<std::string>("yes");
ValueBase * v4 = NewValue(true);

std::vector<ValueBase *> vec;

vec.push_back(v1);
vec.push_back(v2);
vec.push_back(v3);
vec.push_back(v4);
vec.push_back(NewValue(2350983444325345ll));

for (const auto & entry : vec)
{
    std::cout << *entry << " ";
}
std::cout << "\n";

请注意,您通常不需要NewValue的模板参数,因为它将被推导出来。使用"yes"时,这不起作用,因为Value<T>将使用T = char [4]进行实例化,这需要您在构造函数中使用strcpy。我发现明确指出如上所述需要转换是非常好的。如果您隐含地喜欢这个,请执行重载:

Value<std::string> * NewValue(const char * v)
{
    return new Value<std::string>(v);
}

确保手动删除内容

for (const auto & entry : vec)
{
    delete entry;
}
vec.clear();

或使用std::unique_ptr代替裸指针:

template<typename T> std::unique_ptr<Value<T>> UniqueValue(const T & v)
{
    return std::unique_ptr<Value<T>>(new Value<T>(v));
}

std::vector<std::unique_ptr<ValueBase>> vec;
vec.push_back(NewValue(4.5));

如果扩展Value并且事实证明你需要在析构函数中执行某些操作,则必须实现复制构造函数,赋值运算符以及可能的移动构造函数和移动赋值运算符。 (&#34;三个规则&#34;或&#34;五个规则&#34;。)在上面的版本中,&#34;零规则&#34;仍然适用,因为析构函数仍然与隐式定义的析构函数相同(= default)。

如果添加析构函数,它应该是虚拟的。否则,如果删除VirtualBase指针,可能会导致内存泄漏。

答案 2 :(得分:1)

当然你可以做模板,但你也需要做多态。

class StupidAndEmpty {} // add virtual destructor if Value needs a destructor

template<class dType>
Value : StupidAndEmpty {
// do smart things with dType
}

vector<StupidAndEmpty *> notSoStupid;

唯一的问题是当你从矢量中取回它们时如何使用它们。