【问题标题】:Generic Type Container implemented with Templates使用模板实现的通用类型容器
【发布时间】:2014-09-29 16:22:50
【问题描述】:

我有一个通常可以容纳任何(原始)类型的类:

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&lt;Value *&gt; 这样的事情了。这是正确的,还是我错过了可以在这种情况下提供帮助的模板的某些方面?

【问题讨论】:

  • 如果你想使用一个可能有不同类型的值的向量,你不能使用模板。假设一次只有一种值类型处于活动状态,您应该查看与未使用类型相同的内存的联合。
  • @matsjoyce 或 boost::variant,用于有限的已知类型集。

标签: c++ templates containers


【解决方案1】:

您只需要模板一的父类:

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

【讨论】:

  • 我没想到。当您将其推入向量时,您仍然必须指定类型,但至少现在(在我看来)模板比以前更可行。谢谢!
  • 这并没有回答第一个问题,但是:“但是,据我所知,你总是必须指定类型,这有点违背了类的全部目的。”
【解决方案2】:

第一个问题:使用模板工厂函数而不是 new。

第二个问题:使用通用基类。

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

例子:

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&lt;T&gt; 将用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,结果你需要在析构函数中做一些事情,你必须实现复制构造函数、赋值操作符,可能还有移动构造函数和移动赋值操作符。 (“三规则”或“五规则”。)在上述版本中,“零规则”仍然适用,因为析构函数仍然与隐式定义的析构函数相同(= default)。

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

【讨论】:

    【解决方案3】:

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

    class StupidAndEmpty {} // add virtual destructor if Value needs a destructor
    
    template<class dType>
    Value : StupidAndEmpty {
    // do smart things with dType
    }
    
    vector<StupidAndEmpty *> notSoStupid;
    

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

    【讨论】:

      猜你喜欢
      • 2021-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多