【问题标题】:C++ operator overloading and implicit conversionC++ 运算符重载和隐式转换
【发布时间】:2010-12-09 02:59:26
【问题描述】:

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

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 )。当然,我也可以重载浮点版本,但这对我来说似乎是代码的组合爆炸。是否有任何解决方法(或者我的课程设计错误)?如何告诉编译器仅使用整数调用重载版本的 operator*?

【问题讨论】:

  • 我不相信你可以。
  • 另一方面,采用单个参数(更何况是内置参数)的构造函数可能应该被声明为显式,这当然会阻止提升......但也可以防止错误。跨度>
  • 另一个“显式”倡导者 =) 隐式构造函数很有帮助,前提是您意识到它们在做什么。设计一个函数,接受 CFixed 参数并在那里传递整数真是太好了!

标签: c++ operator-overloading implicit-conversion


【解决方案1】:

如果您有可以仅使用一个参数调用的构造函数,则您有效地创建了一个隐式转换运算符。在您的示例中,无论在哪里需要 CFixed,都可以通过 intfloat。这当然是危险的,因为当您忘记包含某些函数的声明时,编译器可能会默默地生成调用错误函数的代码,而不是向您咆哮。

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

你必须把你的班级改成这样:

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

我发现这条规则很少有例外。 (std::string::string(const char*) 比较有名。)

编辑:对不起,我错过了不允许从 intfloat 的隐式转换的要点。

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

【讨论】:

  • 不是问题的重点。如果我的构造函数是显式的,我该如何写 Fixed * 10??
  • 谢谢!你的编辑更有帮助(虽然有点悲观=)
  • @SadSido:Bill 可能找到了解决方案,尽管 Pavel 是对的,但在目前的形式下,这只会产生编译错误。
  • 其实这个答案几乎是对的。您可以保留 float 的隐式构造函数,而只需将 int 设为显式。由于您已经为 int 情况编写了覆盖,因此您实际上并不需要这些情况的隐式转换。这将消除歧义。
  • @Adrian:为什么那个类需要隐式转换?
【解决方案2】:

你也应该重载float 类型。从 int 到用户指定类型 (CFixed) 的转换优先级低于到 float 的内置浮点整数转换。所以编译器总是会选择带有int的函数,除非你也添加带有float的函数。

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

我好像也忘记了。 :-( UncleBens 报告仅添加浮点数并不能解决问题,因为还应添加带有double 的版本。但无论如何添加与内置类型相关的多个运算符是乏味的,但不会导致组合提升。

【讨论】:

  • @UncleBens , double 将转换为 float
  • 它对我说“模棱两可的过载”。尝试CFixed(0) * 1.0,Comeau 特别告诉我operator*(CFixed, int)operator*(CFixed, float) 都是CFixed * double 的平等候选人
【解决方案3】:

假设您希望为任何整数类型选择专用版本(而不仅仅是 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&lt;boost::is_same&lt;T, int&gt;, CFixed&gt;::type ...

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

【讨论】:

  • 我还没有尝试过,但是编译器不应该在模板版本之前更喜欢函数的非模板版本吗?我认为您的“主要”将调用“通用 CFixed”版本 4 次...
  • 如果模板有更好的匹配,它会更喜欢模板。 enable_if 仅在满足条件时才使重载成为候选者,并且在浮动的情况下,只有第一个重载是唯一可供选择的重载。
  • 嘿。有用!我想这比重载浮点运算符要好得多(无需重复代码)...
【解决方案4】:

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

您可以避免使用模板编写的 operator 函数的爆炸式增长,但是:

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

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

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

【讨论】:

  • 模板内部会发生什么? :-) Ẹ完全相同!
  • @Pavel:实际上,我认为比尔是对的(甚至认为他可能没有意识到这一点:))。看看这个:template &lt;class T&gt; CFixed operator* ( const CFixed&amp; a, T b ) { return a*CFixed(b); } 这不应该是 OP 想要的吗?
  • 这是个坏主意,因为任何对operator* 的调用都会考虑重载此类模板,其中一个参数的类型为CFixed 或可转换为它,即使T 实际上不是*CFixed 可用的类型 - 它仍然可以选择该重载,然后在尝试实例化它时失败。由于错误将出现在 operator* 的正文中,因此 SFINAE 不会启动。
  • @sbi:它做了 OP 想要的,但它也创建了一个 CFixed 临时,这是他想要避免的。他最初是从一个operator*(CFixed, CFixed) 开始的,它实际上已经完成了他想要的一切;但是如果他提前知道int,他可以更有效地实现operator*(CFixed, int),这就是他想要一个单独的运算符的原因。这并不能解决这个问题。
  • @Pavel:一开始我也是这么想的(看我的回答),但现在我想,为了防止组合爆炸,他宁愿忍受暂时的。当然,这仍然会导致 SFINAE 无法发挥作用。(也许这可以通过一些默认模板参数来解决?)
【解决方案5】:

如何转换explicit

【讨论】:

  • 这有什么帮助?发帖人希望在将浮点数传递给运算符 * 时调用 CFixed(float) - 添加显式关键字会降低这种可能性。
猜你喜欢
  • 2014-08-18
  • 1970-01-01
  • 1970-01-01
  • 2010-10-27
  • 2013-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多