Классы-обертки для примитивных типов данных

при разработке решения иногда может быть удобно предоставлять классы-оболочки для примитивных типов данных. Рассмотрим класс, представляющий числовое значение, будь то double, a float или 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. Надеюсь, это все объясняет.

7 ответов


в 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 class и увеличьте его таким образом.

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++


если


сделать оператор преобразования частным и иметь функцию друга сделать преобразование внутри него.

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> уничтожается, и это обновления деструктора, это parent Number с обновленным значением. Это имеет дополнительное удобство, требующее только незначительных изменений в Number класса.

очевидно, что здесь есть какая-то опасность, если вы позвоните to_N() дважды в одном и том же операторе два int&не будут синхронизированы, или если кто-то принимает 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() на любой объект a типа any. Вы можете создать дополнительные оболочки свободных функций на этой идее, например void inc(any & a) { a.inc(); }.