一元加运算符有实际用途吗?

31

一元+运算符是否只是为了与一元-运算符对称而被包含在C++中,还是它在C++代码中具有实际用途?

在这里搜索,我发现了C语言中一元'+'运算符的作用是什么?,但那里唯一有用的情况涉及预处理器宏。虽然这些很有用,但它们似乎只是一些不太常见的情况,并且涉及到宏。是否有任何涉及更常见C++代码的用例?


7
数学角度来看?不是的。但它可能在与类的运算符重载中找到用途。 - ApproachingDarknessFish
1
@ValekHalfHeart:是的,但是任何除了return *this;之外的重载都会被视为滥用。 - rodrigo
@rodrigo 但是它仍然可能对this产生副作用,这是否是良好或有用的DSL设计是另一个问题。 - πάντα ῥεῖ
1
@ArneMertz:尽管Boost.Spirit很好用,但它确实是一种语言滥用。并不是说它没有任何优点,但运算符重载并不是为了做这种事情而设计的,当你意识到你无法改变运算符优先级并且你对旧运算符的新含义有些模糊时(cout << a & b;,任何人都可以看出来),这一点就显而易见了。 - rodrigo
@ApproachingDarknessFish:它可能在类的运算符重载中找到用途。你能否通过举例来解释一下? - Destructor
@rodrigo,首先,这不仅涉及返回 *this,还包括通过复制返回它。其次,我发现 operator+ 有更多平凡的用途,特别是对于作为值的引用(代理)的类,而不是值本身。请参见下面的示例。换句话说,operator+ 可能不会完全返回 *this(即“self”),但可以返回具有与“self”“等效”的值的东西。这不是滥用,因为它有一个合理的 operator+,并不是完全实现为 return *this; - alfC
9个回答

34
char ch = 'a';
std::cout << ch << '\n';
std::cout << +ch << '\n';

第一个插入将字符a写入cout。第二个插入将ch的数值写入cout。但这有点晦涩,它依赖于编译器对+运算符应用整数提升。


该数字值是否为其ASCII等价值? - David G
1
@David - 如果使用ASCII编译器,是可以的。但对于其他编码(很少见),则不行。 - Pete Becker
似乎对我不起作用 - http://stacked-crooked.com/view?id=235071220bc8ba0e688b4b58051e182d 这可能是罕见的编码吗? - David G
@David - 不知道你的程序出了什么问题,但104是ASCII码中的'h'。哦,抱歉,我发现我的代码有一个错误,我已经修复了。希望这不会让你太困惑。 - Pete Becker
在推广方面,是否有比更明确有意义的转换更有优势?它可能有效,甚至可能是一些人重复使用的习语,但作为审阅者,我必须查看并确定是否有代码丢失(并担心一元+与二元+<<的优先级)。 - Greg

22

使用一元的 - 操作符来表达对称性并不完全没有用处;它可以用于强调:

const int foo = -1;
const int bar = +1;

一个过载的一元+运算符可以用来表示一个操作,它产生与其操作数相同的逻辑值,同时执行一些非平凡的计算。(我曾经见过Ada中使用这种方式进行类型转换,允许重载一元+,但不允许转换。) 我手头没有一个好的C++示例,而且有人可能会认为这是糟糕的风格。(不过,我见过很多有关重载<<的抱怨。)

至于C++为什么有它,可能主要是为了与C保持一致,C在1989年的ANSI标准中添加了它。 C Rationale 只是说:

一元加号从几个实现中被C89委员会采纳,以保持与一元减号的对称性。


11
如果您的类明确避免任何数值语义,那么任何运算符重载都不会“像整数一样执行”。在这种情况下,一元加号可以得到任何意义,远不止返回* this
著名例子:Boost.Spirit的一元加号用于嵌入式EBNF的Kleene Plus生成一个解析器规则,使其参数(也是解析器规则)匹配一次或多次。

2
我希望我们也能重载 @$# - Mooing Duck
@MooingDuck 而且,如果可以的话,我们能否有能力创建自己的运算符,使用任何组合 +-*/%=!<>~^|&*.[]()@#$ - Blacklight Shining
1
大多数这样的语句都会使解析变得模糊。如果编译器看到 a+-b,那是内置函数还是用户重载? - Mooing Duck
@MooingDuck 如果存在这样的重载,那么它就是一个重载;否则它就是 a + (-b)。 :D - Blacklight Shining

5
一元操作符 + 将左值转换为右值:
struct A {
  static const int value = 1;
};

// ...

int x = std::min(0, A::value);

糟糕!这段代码无法链接,因为有人忘记定义(以及声明)A::valuestd::min按引用获取其参数,因此A::value必须具有地址,以便引用可以绑定到它上(从技术上讲,一个定义规则指出它必须在程序中恰好定义一次)。

不过,一元加号来拯救:

int x = std::min(0, +A::value);

一元加号操作符会创建一个临时变量,其值与原变量相同,引用将绑定到临时变量上,因此我们可以通过这种方式规避缺失的定义。

虽然并不经常需要使用该方法,但这是一元加号操作符的实用用途之一。


3

一元加号应用整数提升。@PeteBecker的回答展示了一种有用的方式。

另外,需要注意的是,未作用域限定的枚举类型会被提升为一个整数类型,该类型可以表示enum中的所有值。因此,在C++03中,即使没有C++11的std::underlying_type<T>,也可以这样做:

enum MyBitMask {
    Flag1 = 0x1,
    Flag2 = 0x2,
    Flag3 = 0x4,
    Flag4 = 0x8000000
};

inline MyBitMask operator&(MyBitMask x, MyBitMask y) {
    return static_cast<MyBitMask>( +x & +y );
}

inline MyBitMask operator|(MyBitMask x, MyBitMask y) {
    return static_cast<MyBitMask>( +x | +y );
}

3

除了其他事情外,+ 还将 lambda 转换为函数指针。通常情况下,这种转换会自动发生,但有时候不会。

例如,以下代码无法编译:

std::array arr{
    [](int x){return x*x;},
    [](int x){return x*x*x;},
};

您可以通过将函数指针类型指定为std::array模板参数来使其工作,或者您可以这样做:

std::array arr{
    +[](int x){return x*x;},
    +[](int x){return x*x*x;},
};

1
在这种特定情况下,有没有理由优先选择重载的 operator+ 函数而不是 operator*?我不明白为什么在需要函数指针时 operator+ 被认为是更直观的选项。 - 303
3
很有趣,我从来没有想到 * 在这里也可以工作。但是它们都没有被重载。对我来说,+ 更直观,因为它只执行转换,什么都不做。而 * 执行指针转换,然后解引用指针,结果隐式地转换回指针(除非你真的需要一个函数的引用)。 - HolyBlackCat

1
有点晚了,但这里有一个非常扭曲的用法,我偶然发现。 显然,当设计防范可能遇到空预处理器令牌的保障措施时,+运算符可能会很有用(如果可能不是严格必要的)。 请参见this post以获取更深入的讨论。
这很实用,但绝不令人愉快。

0

由于算术变量的运算符+会生成一个新值。 我使用它来生成引用类型(代理)的值副本。

template<class T> class ref_of{
   T* impl_; // or a more complicated implementation
public:
   T operator+() const{return *impl_;}
   operator T&()&{return *impl_;}
}

另一个选择是使用operator*,但这样ref_of可能会被误认为是一个类似指针的对象。

0

由于对于算术变量,operator+ 会生成一个 值, 因此我通常将其用于生成类似引用的(代理)类型的值副本。

template<class T> class ref_of{
   T* impl_; // or a more complicated implementation
public:
   T operator+() const{return *impl_;} 
   operator T&()&{return *impl_;}
}
...
ref_of<T> r = t;
auto s = +r; // this forces a copy

另一个选择是使用operator*,但这样ref_of可能会被误认为是类似指针的对象。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接