Классы-обертки для примитивных типов данных
при разработке решения иногда может быть удобно предоставлять классы-оболочки для примитивных типов данных. Рассмотрим класс, представляющий числовое значение, будь то 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(); }
.