基元数据类型的包装器类

Wrapper Classes for Primitive Data Types

本文关键字:包装 数据类型      更新时间:2023-10-16

在设计解决方案时,有时为基元数据类型提供包装类可能很方便。考虑一个表示数值的类,无论是doublefloat还是int

class Number {
private:
    double val;
public:
    Number(int n) : val(n) { }
    Number(float n) : val(n) { }
    Number(double n) : val(n) { }
    // Assume copy constructors and assignment operators exist
    Number& add(const Number& other) {
        val += other.val;
        return *this;
    }
    int to_int() const { return (int) val; }
    float to_float() const { return (float) val; }
    double to_double() const { return val; }
};

现在假设我有一个这样的函数:

void advanced_increment(Number& n) {
    n.add(1);
}

我会这样使用这个函数:

Number n(2);
advanced_increment(n); // n = 3

这听起来很简单。但是如果函数是这样的呢?

void primitive_increment(int& n) {
    ++n;
}

请注意,增量只是一个示例。假设该函数将对基元数据类型执行更复杂的操作,而它们也应该能够在没有任何问题的情况下对Number类型执行这些操作。

我该如何像以前一样使用该函数?如:

Number n(2);
primitive_increment(n);

如何使我的Number类与primitive_increment兼容?如何为基元数据类型创建一个包装类,使其在需要这些数据类型的任何地方都兼容?

到目前为止,我只找到了两个解决方案。一种是创建一个像double& Number::get_value()这样的函数,然后像primitive_increment(n.get_value());一样使用它。第二种解决方案是创建隐式转换方法,如Number::operator int&();但这些可能会导致许多不明确的调用,并使代码变得混乱。

我想知道是否有其他解决方案可以实现这些类型的包装器并保留其原始功能。

更新:

为了进一步澄清,在实际项目中,这里的意图是在设计此类解决方案时,使所有数据类型都派生自一个基类,该基类通常被称为Object。限制条件是不应使用外部库。因此,如果我有一个具有指向类型Object的指针的容器,那么它应该能够保存任何任意值,无论是否为基元,并执行Object上允许的任何基元操作。我希望这能更好地解释它。

C++11具有显式运算符重载。

struct silly_wrapper {
  int foo;
  explicit operator int&() { return foo; }
};
void primitive_increment(int& x) { ++x; }

int main()
{
   silly_wrapper x;
   primitive_increment(x); // works
   x += 1; // doesn't work - can't implicitly cast
}
class Number {
    enum ValType {DoubleType, IntType} CurType;
    union {
        double DoubleVal;
        int IntVal;
    };
public:
    Number(int n) : IntVal(n), CurType(int) { }
    Number(float n) : DoubleVal(n), CurType(DoubleType) { }
    Number(double n) : DoubleVal(n), CurType(DoubleType) { }
   // Assume copy constructors and assignment operators exist
    Number& add(const Number& other) {
        switch(CurType) {
        case DoubleType: DoubleVal += other.to_double(); break;
        case IntType: IntVal+= other.to_int(); break;
        }
        return *this;
    }
    int& to_int() { 
        switch(CurType) {
        case DoubleType: IntVal = DoubleVal; CurType = IntType; break;
        //case IntType: DoubleVal = IntVal; CurType = DoubleType; break;
        }
        return IntVal; 
    }
    const int to_int() const { 
        switch(CurType) {
        case DoubleType: return (int)DoubleVal;
        case IntType: return (int)IntVal;
        }
    }
    const float to_float() const { 
        switch(CurType) {
        case DoubleType: return (float)DoubleVal;
        case IntType: return (float)IntVal;
        }
    }
    double& to_double() { 
        switch(CurType) {
        //case DoubleType: IntVal = DoubleVal; CurType = IntType; break;
        case IntType: DoubleVal = IntVal; CurType = DoubleType; break;
        }
        return DoubleVal; 
    }
    const double to_double() const { 
        switch(CurType) {
        case DoubleType: return (double)DoubleVal;
        case IntType: return (double)IntVal;
        }
    }
};
void primitive_increment(int& n) {
    ++n;
}
int main() {
    Number pi(3.1415);
    primitive_increment(pi.to_int());
    //pi now is 4
    return 0;
}

我承认这很尴尬,不是理想的情况,但它解决了给定的问题。

而不是将其提供给primitive_increment。您应该重载Number类的++运算符,并以这种方式递增。

Number& operator++() { ++val; return *this;}
Number& operator+=(const Number& rhs) { val += rhs.Val; return *this;}
Number operator+(const Number& rhs) { Number t(*this); t+=rhs; return t;}

请参阅:C和C++中的运算符

如果您的Number类没有实现int的子集,则无法实现。例如,如果Number类包含值INT_MAX,并且可以保存值INT_MAX+1,则会给出错误的结果。如果您的Number类为int的子集建模,那么转换为int并返回当然是一种选择。

除此之外,您唯一的机会就是重写函数以接受Number对象。理想情况下,将它作为一个模板,这样它就可以与intNumber(以及任何其他当前或未来提供类似int接口的类(一起工作。

将转换运算符设为私有运算符,并在其内部使用友元函数进行转换。

class silly_wrapper {
private:
  int foo;
  float bar;
  operator int&() { return foo; }
  template <typename T>
  friend void primitive_increment(T& x) { ++static_cast<int&>(x); }
};

int main()
{
   silly_wrapper x;
   primitive_increment(x); // works
   int i;
   primitive_increment(i); // works
   int& r = static_cast<int&>(x); // can't convert - operator is private
}

这里有一个我刚刚想到的更奇怪的答案:

class Number; 
template<class par, class base>
class NumberProxy {
    base Val;
    par* parent;
    NumberProxy(par* p, base v) :parent(p), Val(v) {}
    NumberProxy(const NumberProxy& rhs) :parent(rhs.parent), Val(rhs.Val) {}
    ~NumberProxy() { *parent = Val; }
    NumberProxy& operator=(const NumberProxy& rhs) {Val = rhs.Val; return *this}
    operator base& {return Val;}
};
class Number {
private:
    double val;
public:
    Number(int n) : val(n) { }
    Number(float n) : val(n) { }
    Number(double n) : val(n) { }
    // Assume copy constructors and assignment operators exist        
    int to_int() const { return (int) val; }
    float to_float() const { return (float) val; }
    double to_double() const { return val; }
    NumberProxy<Number,int> to_int() { return NumberProxy<Number,int>(this,val); }
    NumberProxy<Number,float> to_float() { return NumberProxy<Number,float>(this,val); }
    NumberProxy<Number,double> to_double() { return NumberProxy<Number,double>(this,val); }
};
void primitive_increment(int& n) {
    ++n;
}
int main() {
    Number pi(3.1415);
    primitive_increment(pi.to_int());
    //pi now is 4
    return 0;
}

Number.to_int()返回一个NumberProxy<int>,它可以隐式转换为函数操作的int&。当函数和表达式完成时,临时NumberProxy<int>被销毁,它的析构函数用更新的值更新它的父Number。这增加了只需要对Number类进行微小修改的便利性。

显然,这里有一些危险,如果你在同一个语句中调用to_N()两次,两个int&amps不会同步,或者如果有人使用int&超过声明末尾。

模板化的免费函数如何:

class IncTagIntegral{};
class IncTagNonintegral{};
template <bool> struct IncTag { typedef IncTagNonintegral type; }
template <> struct IncTag<true> { typedef IncTagIntegral type; }
template <typename T> void inc_impl(T & x, IncTagIntegral)
{
  ++x;
}
template <typename T> void inc_impl(T & x, IncTagNonintegral)
{
  x += T(1);
}

template <typename T> void primitive_increment(T & x)
{
  inc_impl<T>(x, typename IncTag<std::is_integral<T>::value>::type());
}
template <> void primitive_increment(Number & x)
{
  // whatever
}

这种方法可以推广到需要应用于现有类型和您自己的类型的其他函数。


这是另一个长镜头,这次使用类型擦除:

struct TEBase
{
   virtual void inc() = 0;
}
struct any
{
  template <typename T> any(const T &);
  void inc() { impl->inc(); }
private:
  TEBase * impl;
};
template <typename T> struct TEImpl : public TEBase
{
  virtual void inc() { /* implement */ }
  // ...
}; // and provide specializations!
template <typename T> any::any<T>(const T & t) : impl(new TEImpl<T>(t)) { }

关键是,您可以通过专门化的方式提供TEImpl<T>::inc()的不同具体实现,但您可以将a.inc()用于任何类型为any对象a。您可以基于这个想法构建额外的免费函数包装器,如void inc(any & a) { a.inc(); }