Sou*_*mar 16 c++ operator-overloading
我不明白为什么在下面的代码中表达式C c3 = 5 + c;
不会被编译,尽管5可以转换为类型C,就像在前面的语句中一样.
#include <iostream>
class C
{
int m_value;
public:
C(int value): m_value(value) {} ;
int get_value() { return m_value; } ;
C operator+(C rhs) { return C(rhs.m_value+m_value); }
};
int main()
{
C c = 10;
C c2 = c + 5; // Works fine. 5 is converted to type C and the operator + is called
C c3 = 5 + c; // Not working: compiler error. Question: Why is 5 not converted to type C??
std::cout << c.get_value() << std::endl; // output 10
std::cout << c2.get_value() << std::endl; // output 15
}
Run Code Online (Sandbox Code Playgroud)
son*_*yao 21
因为如果重载运算符作为类的成员函数,则只能在该类的对象用作左操作数时调用它.(并且左操作数成为*this要调用的成员函数的隐式对象.)
二元运算符通常实现为非成员以保持对称(例如,当添加复数和整数时,如果operator +是复杂类型的成员函数,则只
complex+integer编译,而不是integer+complex).
(强调我的)
对于具有cv非限定版本为T1的类型的操作数的一元运算符@,以及具有cv非限定版本为T1的类型的左操作数的二元运算符@和具有cv不合格的类型的右操作数版本为T2,四组候选函数,指定成员候选人,非成员候选人,内置候选人和重写候选人,构造如下:
- (3.1)如果T1是完整的类类型或当前正在定义的类,则成员候选集合是T1 :: operator @([over.call.func])的限定查找的结果; 否则,成员候选人是空的.
这意味着如果左操作数的类型不是类类型,则成员候选集合为空; 不会考虑重载的运算符(作为成员函数).
您可以将其作为非成员函数重载,以允许对左右操作数进行隐式转换.
C operator+(C lhs, C rhs) { return C(lhs.get_value() + rhs.get_value()); }
Run Code Online (Sandbox Code Playgroud)
然后两者c + 5或5 + c将工作正常.
顺便说一句:这将导致被构造一个temporaray对象(从int到C)对非成员函数被调用; 如果你关心它,你可以添加所有三个可能的重载,如下所示.另请注意,这是一个权衡问题.
C operator+(C lhs, C rhs) { return C(lhs.get_value() + rhs.get_value()); }
C operator+(C lhs, int rhs) { return C(lhs.get_value() + rhs); }
C operator+(int lhs, C rhs) { return C(lhs + rhs.get_value()); }
Run Code Online (Sandbox Code Playgroud)
而在这里 "再约一些建议时使用正常的,朋友或成员函数重载.
在大多数情况下,语言会由您决定是否要使用重载的普通/好友或成员函数版本.但是,两者中的一个通常是比另一个更好的选择.
处理不修改左操作数的二元运算符(例如operator +)时,通常首选普通函数或友元函数版本,因为它适用于所有参数类型(即使左操作数不是类对象,或者是一个不可修改的类).普通或友元函数版本具有"对称性"的额外好处,因为所有操作数都变为显式参数(而不是左操作数变为*this且右操作数变为显式参数).
当处理修改左操作数的二元运算符(例如operator + =)时,通常首选成员函数版本.在这些情况下,最左边的操作数将始终是类类型,并且正在修改的对象成为*指向的对象,这很自然.因为最右边的操作数变成了一个显式参数,所以不会混淆谁被修改以及谁被评估.
您面临将某些运算符重载定义为自由函数的原因,即需要隐式转换时.要了解幕后发生了什么,请考虑运算符重载调用的详细形式:
C c2 = c.operator+(5); // Ok, c has this member function
C c3 = 5.operator+(c); // No way, this is an integer without members
Run Code Online (Sandbox Code Playgroud)
你显然可以做一个明确的C结构,如
C c3 = C{5} + c;
Run Code Online (Sandbox Code Playgroud)
但这不适用于算术值类型C.要使隐式构造成为可能,请将重载定义为自由函数
auto operator + (C lhs, const C& rhs)
{
lhs += rhs;
return lhs;
}
Run Code Online (Sandbox Code Playgroud)
现在,左侧操作数没有限制.请注意,运算符是根据+=(您必须实现它以使上述代码编译)来实现的,这是本线程中指出的良好实践:当您为operator +自定义类型提供二进制文件时,该类型的用户将预计operator +=也将有空.因此,为了减少代码的重复,它通常是很好的实施+来讲+=(同为其它算术操作数).
进一步注意,这些操作数通常需要大量的样板代码.要减少这种情况,请考虑例如Boost运算符库.要根据最少量的实际手写代码生成所有标准算术运算符:
#include <boost/operators.hpp>
class C : private boost::arithmetic<C>
// ^^^^^^^^^^^^^^^^^^^^
// where the magic happens (Barton-Nackmann trick)
{
int m_value ;
public:
C(int value): m_value(value) {} ;
C& operator+=(const C& rhs) { m_value += rhs.m_value; return *this; }
C& operator-=(const C& rhs) { m_value -= rhs.m_value; return *this; }
C& operator*=(const C& rhs) { m_value *= rhs.m_value; return *this; }
C& operator/=(const C& rhs) { m_value /= rhs.m_value; return *this; }
const C& operator+() const { return *this; }
C operator-() const { return {-m_value}; }
int get_value() { return m_value; } ;
};
Run Code Online (Sandbox Code Playgroud)
这是关于为什么您建议编译器可以将左手参数隐式转换为 a 的额外评论(有点“简化广告荒谬”)C,本质上,这会打开一堆蠕虫。实际的语言规则说,简单地说,在应用转换之前,名称查找 - 用于函数调用和对(用户声明的)运算符的调用 - 以找到候选集。在这一点上,还没有考虑操作数类型,但范围很好。因此,第一个参数的类型呢 就用户声明的运算符而言,仅当其第一个参数是声明它的(cv 限定的)类类型时才在范围内。当找到候选集时,编译器然后尝试应用转换规则和排名候选人等
(因此,您的问题有点误导,因为在您的示例中,我们甚至没有涉及转换逻辑,而是名称解析已经空了。)
现在,想象一下我们可以简单地改变语言,说第一个参数也可以在名称解析之前被转换。这里需要一点手动操作,因为这意味着我们必须进行转换,查找名称,然后再次进行转换,因此在实践中这将如何工作当然不清楚。无论如何,看看这个例子然后:
struct B;
struct A
{
A(int);
A operator +(B) const;
};
struct B
{
B(int);
B operator +(B) const;
};
Run Code Online (Sandbox Code Playgroud)
现在,应该1 + B{3}怎么办?显然,它可以转换为B{1} + B{3}. 但谁能说我们不能做A{1} + B{3}呢?为什么B's 的构造函数比A's 更受欢迎?当然,我们可以争辩说,两者B都应该是首选,因为,看看它有多漂亮和对称B{...}+B{...}(好吧,我有点开玩笑)。或者我们可以采取更安全的方式,如果它包含这样的歧义,就说程序是格式错误的。但是还有更多的极端情况需要考虑,例如,如果B构造了 if的构造函数会怎样explicit——编译器(仍然或新的)应该出错,还是应该悄悄地切换到可用的隐式转换A?
另一个不明显的问题是应该考虑哪些类型的范围(例如命名空间)?如果您operator +在例如全局命名空间范围内使用,那肯定会令人惊讶,并且编译器会挖掘出某种类型__gnucxx::__internal::__cogwheels::__do_something_impl,将操作数隐式转换为它,然后对其执行操作。
另请注意,即使可以以合理和干净的方式指定此功能,也可能会产生相当大的编译时间成本(实际上,重载解析已经是编译 C++ 时最大的成本之一,也是编译 C++ 的原因之一代码可能比编译 C 花费更长的时间)。
特尔;博士:
| 归档时间: |
|
| 查看次数: |
924 次 |
| 最近记录: |