C++运算符重载和隐式转换

发布于 2024-08-08 19:17:48 字数 601 浏览 5 评论 0 原文

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

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

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

一切正常。我可以写 3 * CFixed(0) 和 CFixed(3) * 10.0f。但现在我意识到,我可以使用整数操作数更有效地实现operator*。所以我重载它:

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

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

I have a class that encapsulates some arithmetic, let's say fixed point calculations. I like the idea of overloading arithmetic operators, so I write the following:

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

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

It all works. I can write 3 * CFixed(0) and CFixed(3) * 10.0f. But now I realize, I can implement operator* with an integer operand much more effective. So I overload it:

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

It still works, but now CFixed(0) * 10.0f calls overloaded version, converting float to int ( and I expected it to convert float to CFixed ). Of course, I can overload a float versions as well, but it seems a combinatorial explosion of code for me. Is there any workaround (or am I designing my class wrong)? How can I tell the compiler to call overloaded version of operator* ONLY with ints?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

把人绕傻吧 2024-08-15 19:17:48

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

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

看来我也已经失去了踪迹。 :-( UncleBens 报告仅添加 float 并不能解决问题,因为 double 也应该添加,但无论如何,添加多个与内置类型相关的运算符是乏味的,但不会导致组合提升。

You should overload with float type as well. Conversion from int to user-specified type (CFixed) is of lower priority than built-in floating-integral conversion to float. So the compiler will always choose function with int, unless you add function with float as well.

For more details, read 13.3 section of C++03 standard. Feel the pain.

It seems that I've lost track of it too. :-( UncleBens reports that adding float only doesn't solve the problem, as version with double should be added as well. But in any case adding several operators related to built-in types is tedious, but doesn't result in a combinatorial boost.

一梦等七年七年为一梦 2024-08-15 19:17:48

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

因此,一个好的经验法则是,每当您编写只能使用一个参数调用的构造函数时(请注意,这个 foo(int i, bool b = false) 可以使用一个参数调用参数,即使它需要两个参数),您应该使该构造函数显式,除非您确实希望启动隐式转换。显式构造函数不被使用隐式转换的编译器。

您必须将您的课程更改为:

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

我发现这条规则很少有例外。 (std::string::string(const char*) 是一个相当著名的。)

编辑: 抱歉,我错过了关于不允许隐式转换的要点从intfloat

我认为防止这种情况的唯一方法是同时提供 float 运算符。

If you have constructors which can be invoked with just one argument, you effectively created an implicit conversion operator. In your example, wherever a CFixed is needed, both an int and a float can be passed. This is of course dangerous, because the compiler might silently generate code calling the wrong function instead of barking at you when you forgot to include some function's declaration.

Therefore a good rule of thumb says that, whenever you're writing constructors that can be called with just one argument (note that this one foo(int i, bool b = false) can be called with one argument, too, even though it takes two arguments), you should make that constructor explicit, unless you really want implicit conversion to kick in. explicit constructors are not used by the compiler for implicit conversions.

You would have to change your class to this:

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

I have found that there are very few exceptions to this rule. (std::string::string(const char*) is a rather famous one.)

Edit: I'm sorry, I missed the point about not allowing implicit conversions from int to float.

The only way I see to prevent this is to provide the operators for float as well.

孤寂小茶 2024-08-15 19:17:48

假设您希望为任何整数类型选择专用版本(而不仅仅是 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;
}

当然,您也可以使用不同的条件使这些重载仅在 T=int 时才可用: typename boost::enable_if, CFixed>::type ...

至于设计类,也许您可​​以更多地依赖模板,例如,构造函数可以是模板,并且您是否需要区分整数。和真实类型,应该可以使用这种技术。

Assuming you'd like the specialized version to be picked for any integral type (and not just int in particular, one thing you could do is provide that as a template function and use Boost.EnableIf to remove those overloads from the available overload set, if the operand is not an integral type.

#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;
}

Naturally, you could also use a different condition to make those overloads available only if T=int: typename boost::enable_if<boost::is_same<T, int>, CFixed>::type ...

As to designing the class, perhaps you could rely on templates more. E.g, the constructor could be a template, and again, should you need to distinguish between integral and real types, it should be possible to employ this technique.

在梵高的星空下 2024-08-15 19:17:48

如何显式转换

How about making the conversion explicit?

无语# 2024-08-15 19:17:48

同意 sbi 的观点,你绝对应该明确你的单参数构造函数。

您可以避免操作员<>中的爆炸但是,您使用模板编写的函数:

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

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

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

Agree with sbi, you should definitely make your single-parameter constructors explicit.

You can avoid an explosion in the operator<> functions you write with templates, however:

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

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

Depending on what code is in the functions, this will only compile with types that you support converting from.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文