在设计解决方案时,有时为原始数据类型提供包装类可能很方便。考虑一个表示数值的类,可以是double
,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
上允许的任何原始操作。我希望这能更好地解释它。
答案 0 :(得分:1)
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;
}
我承认这很尴尬,而不是理想的情况,但它解决了给定的问题。
答案 1 :(得分:1)
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
}
答案 2 :(得分: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;}
答案 3 :(得分:0)
如果您的Number
课程没有实现int
的子集,那么您就是不能这样做。如果例如,它会给出错误的结果您的Number
类包含值INT_MAX
,并且也可以包含值INT_MAX+1
。如果您的Number
班为int
的子集建模,那么转换为int
并返回当然是一种选择。
除此之外,您唯一的机会是重写函数以接受Number
个对象。理想情况下,将其设为模板,以便它可以与int
和Number
一起使用(以及与呈现int
类似接口的任何其他当前或未来类。)< / p>
答案 4 :(得分:0)
将转化运算符设为私有,并让友元函数在其中进行转换。
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
}
答案 5 :(得分:0)
这是我刚才想到的一个更为古怪的答案:
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&amp;'s将不会同步,或者如果有人采取了int&amp;在声明结束之后。
答案 6 :(得分:0)
(这在黑暗中有点像,因为我不完全确定你的整体设计是如何组合在一起的。)
模板化自由函数怎么样:
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
{1}}。您可以在此想法上构建其他自由函数包装器,例如any
。