C++ C++;运算符重载与隐式转换

C++ C++;运算符重载与隐式转换,c++,operator-overloading,implicit-conversion,C++,Operator Overloading,Implicit Conversion,我有一个类封装了一些算术,比如说定点计算。我喜欢重载算术运算符的想法,因此我写了以下内容: class CFixed { CFixed( int ); CFixed( float ); }; CFixed operator* ( const CFixed& a, const CFixed& b ) { ... } 一切正常。我可以写3*CFixed(0)和CFixed(3)*10.0f。但现在我意识到,我可以用整数操作数更有效地实现运算符*。所以我把它超载了:

我有一个类封装了一些算术,比如说定点计算。我喜欢重载算术运算符的想法,因此我写了以下内容:

class CFixed
{
   CFixed( int   );
   CFixed( float );
};

CFixed operator* ( const CFixed& a, const CFixed& b )
{ ... }
一切正常。我可以写3*CFixed(0)和CFixed(3)*10.0f。但现在我意识到,我可以用整数操作数更有效地实现运算符*。所以我把它超载了:

CFixed operator* ( const CFixed& a, int b )
{ ... }
CFixed operator* ( int a, const CFixed& b )
{ ... }

它仍然有效,但现在CFixed(0)*10.0f调用重载版本,将float转换为int(我希望它将float转换为CFixed)。当然,我也可以重载浮点版本,但对我来说,这似乎是一个组合式的代码爆炸。有什么解决办法吗(或者我的课程设计错了)?如何告诉编译器仅使用int调用运算符*的重载版本

进行转换怎么样?

如果您有可以仅用一个参数调用的构造函数,那么您实际上创建了一个隐式转换运算符。在您的示例中,只要需要
CFixed
,就可以传递
int
float
。这当然是危险的,因为编译器可能会悄悄地生成调用错误函数的代码,而不是在您忘记包含某个函数的声明时对您咆哮

因此,一个很好的经验法则是,当您编写只需一个参数即可调用的构造函数时(请注意,这一个
foo(inti,bool b=false)
也可以用一个参数调用,即使它需要两个参数),您应该使该构造函数显式
,除非你真的想要隐式转换<编译器不使用代码>显式
构造函数进行隐式转换

您必须将您的类更改为:

class CFixed
{
   explicit CFixed( int   );
   explicit CFixed( float );
};
我发现这条规则很少有例外。(
std::string::string(const char*)
是一个相当著名的例子。)

编辑:很抱歉,我忽略了不允许从
int
float
的隐式转换的要点


我认为防止这种情况发生的唯一方法是为
float
提供操作符

同意sbi,您应该明确地将单参数构造函数显式化

但是,可以避免使用模板编写的运算符函数爆炸:

template <class T>
CFixed operator* ( const CFixed& a, T b ) 
{ ... } 

template <class T>
CFixed operator* ( T a, const CFixed& b ) 
{ ... } 
模板
固定运算符*(常数固定&a,T b)
{ ... } 
模板
固定运算符*(T a、常数固定和b)
{ ... } 

根据函数中的代码,这将只使用支持从中转换的类型进行编译。

您也应该使用
float
类型重载。从
int
转换为用户指定类型(
CFixed
)的优先级低于内置的浮点积分转换为
float
。因此,编译器将始终选择具有
int
的函数,除非您还添加了具有
float
的函数

有关更多详细信息,请阅读C++03标准的13.3部分。感受痛苦


看来我也不知道了-(报告称,仅添加float并不能解决问题,因为还应添加带有
double
的版本。但在任何情况下,添加几个与内置类型相关的运算符都是乏味的,但不会导致组合增强。

假设您希望为任何整数类型选择专用版本(特别是,不仅仅是int,如果操作数不是整数类型,可以将其作为模板函数提供,并使用Boost.EnableIf从可用重载集中删除这些重载

#include <cstdio>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_integral.hpp>

class CFixed
{
public:
   CFixed( int   ) {}
   CFixed( float ) {}
};

CFixed operator* ( const CFixed& a, const CFixed&  )
{ puts("General CFixed * CFixed"); return a; }

template <class T>
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* ( const CFixed& a, T  )
{ puts("CFixed * [integer type]"); return a; }

template <class T>
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* ( T , const CFixed& b )
{ puts("[integer type] * CFixed"); return b; }


int main()
{
    CFixed(0) * 10.0f;
    5 * CFixed(20.4f);
    3.2f * CFixed(10);
    CFixed(1) * 100u;
}
#包括
#包括
#包括
类固定
{
公众:
CFixed(int){}
固定(浮动){}
};
CFixed运算符*(常数CFixed&a,常数CFixed&)
{puts(“通用CFixed*CFixed”);返回a;}
模板
typename boost::enable_if::type操作符*(const CFixed&a,T)
{puts(“CFixed*[整型]”);返回a;}
模板
typename boost::enable_if::type操作符*(T、const CFixed&b)
{put(“[integer type]*CFixed”);返回b;}
int main()
{
固定(0)*10.0f;
5*C固定(20.4f);
3.2f*CFixed(10);
固定(1)*100u;
}
当然,您也可以使用不同的条件使这些重载仅在T=int:
typename boost::enable\u if::type…


至于设计类,也许你可以更多地依赖模板。例如,构造函数可以是一个模板,同样,如果你需要区分整型和实型,应该可以使用这种技术。

这有什么帮助?海报需要CFixed(float)如果将浮点传递给运算符*,则调用该函数-添加显式关键字将降低这种可能性。模板内部会发生什么情况?:-)Ẹ同样的事情!@Pavel:事实上,我认为Bill是对的(即使他可能没有意识到这一点))。看看这个:
模板固定操作符*(const-CFixed&a,tb){返回a*CFixed(b);}
这不是OP想要的吗?这是一个坏主意,因为这样的模板会被认为是对
操作符*
的任何调用的重载,其中一个参数的类型是
CFixed
或可转换为它,即使
t
实际上不是
*
with
CFixed
可用的类型标签-它仍然可以选择该重载,然后在尝试实例化它时失败。由于错误将在
操作符*
的主体内,SFINAE不会启动。@sbi:它做OP想要的事情,但它也创建了一个
固定的
临时,这是他想要避免的。他最初是从一个
操作符开始的*(CFixed,CFixed)
,实际上已经完成了他想要的一切;但是如果他事先知道它是
int
,他可以更有效地实现
操作符*(CFixed,int)
,这就是他想要se的原因