【问题标题】:How has += operator been implemented in c++?+= 运算符是如何在 C++ 中实现的?
【发布时间】:2020-08-17 21:06:12
【问题描述】:

这是我一直在思考的问题,但从未找到任何资源来说明这个问题的答案。事实上,它不仅适用于+=,也适用于其兄弟姐妹,即-=*=/= 等(当然不是==)。

考虑这个例子,

int a = 5;
a += 4;
//this will make 'a' 9

现在考虑等价的表达式:

a = a + 4;
//This also makes 'a' 9

如果 += 只是 a = a + <rhs of +=> 的简写 重载 + 运算符也应该隐式重载+=,除非另有显式重载。但事实并非如此。这意味着,a += b 不会转换为 a = a + b。但是为什么不以这种方式实施呢?就像在编译期间将其简单地转换为a = a + b 而不是将其作为运算符单独实现不是更容易吗?这也有助于运算符重载,其中a += bab 是同一类的对象不必显式重载,只需重载+ 就足够了?


编辑: this 答案让我的问题变得更加清晰
让我用一个需要重载运算符的例子来解释我的问题:

class A {
    int ivar;
public:
    A() = default;
    A(int par_ivar) : ivar(par_ivar) { }
    A(A& a) {
        this.ivar = a.ivar;
    }
    A(A&& a) noexcept {
        this.ivar = a.ivar;
    }
    A operator+(const A& a) const {
        A temp_a;
        temp_a.ivar = this.ivar + a.ivar;
        return temp_a;
    }
    void operator=(const A& a) {
        this.ivar = a.ivar;
    }
    ~A() = default;
};

现在,让我们看看 2 个程序的结果:
prog1:

int main() {
    A a1(2);
    A a2(3);
    a1 = a1 + a2;  //a1.ivar = 5
    return 0;
}

程序2:

int main() {
    A a1(2);
    A a2(3);
    a1 += a2;  //compilation error!!
    return 0;
}

即使两个程序都打算做,不,做同样的事情,一个编译并运行(希望我的重载是正确的)另一个甚至没有编译!如果 += 被简单地替换为适当的 + 和 =,我们就不会觉得需要显式重载 +=。这是有意的,还是等待添加的功能?

【问题讨论】:

  • “现在考虑到 += 只是一个简写” - 不完全是。 aa += b 中只被评估一次,而a = a + b 将让a 被评估两次。如果a 涉及函数调用,那将是一个主要区别。
  • 运算符不是从其他人生成的(除了在 C++20 中使用/来自<=>):提供operator < 不允许a > b(这在“逻辑上”确实等同于@ 987654350@)。你必须实现所有(甚至通过重用一些)。
  • 没有“背景”。对于类类型+=+ 是不同的运算符
  • 当我为一个类重载自己的++= 运算符时,我按照+= 实现+,而不是相反。

标签: c++ operators default


【解决方案1】:

运算符不是从其他生成的(C++20 中使用/从 除外):

提供operator < 不允许a > b(这确实“逻辑上”等效于b < a)。你必须实现所有(甚至通过重用一些)。

对于类,a += b 不是 a = a + b 的简写

但对于a.operator +=(b)operator +=(a, b)

同样a = a + ba.operator=(operator +(a, b))(或其变体)的简写

在实践中,从operator += 实现operator+ 比反过来执行效率更高。

即使用户可能会根据他们的名字期待类似的行为,它们也是常规函数。

我已经看到一个矩阵迭代器,++it 增加列索引,而it++ 增加行索引。

如果+= 只是a = a + <rhs of +=> 的简写,则重载+ 运算符也应该隐含overload +=,除非以其他方式显式重载。但事实并非如此。这意味着,a += b 不会转换为 a = a + b

(可能)理性不生成可能是性能和控制:

向量(用于数学)或矩阵是很好的例子:

4 种可能的重载

Matrix operator+(Matrix&& lhs, Matrix&& rhs)      { return std::move(lhs += rhs); }
Matrix operator+(Matrix&& lhs, const Matrix& rhs) { return std::move(lhs += rhs); }
Matrix operator+(const Matrix& lhs, Matrix&& rhs) { return std::move(rhs += lhs); } // + is symmetrical :)
Matrix operator+(const Matrix& lhs, const Matrix& rhs) { auto tmp{lhs}; return tmp += rhs; }

该决定的副作用是允许给运算符赋予不同的含义,如“名称运算符”:

if (42 <in> std::vector{4, 8, 15, 16, 23, 42})

【讨论】:

  • 对于内置类型,讨论大多无关紧要,提供这些运算符的是编译器。
  • @d4rk4ng31 • 内置的++= 相互独立,并且都可以追溯到C++ 继承的C。 50 年前,使用非优化编译器,+= 可以编译为与 += 的两个操作码不同的单个操作码,微优化也是如此。现在有了非常强大的编译器优化器,性能方面已经不是问题,+= 可以被认为是多余的。
  • @d4rk4ng31 • 我希望 Jarod42 用额外的信息更新他的出色答案(如果他愿意的话)。实现你自己的编译器,因为一个原始类型只会被读取一次和写入一次,那么是的,+= 可以很容易地用=+ 来实现(然后后端的优化器可以改变它们如果适用于给定的 CPU,则返回优化的 += 等效项)。当然,这不适用于用户类型和用户定义的重载。
  • @d4rk4ng31:您可以创建自己的语言 :) 对于内置,a += b;a = a + b; 提供相同的结果。对于自定义类,它们不需要做同样的事情。
  • @d4rk4ng31:“这是有意的,还是等待添加的功能?”是的,而且可能。
【解决方案2】:

使用a = a + b 将意味着使用copy assignment(因为使用了operator =)。另一方面,a += b 默认是复合赋值。

根据cppreference

复制赋值运算符将对象a的内容替换为b的内容的副本(b没有被修改)。

复合赋值运算符将对象a的内容替换为a的前一个值和b的值之间的二元运算的结果。

使用a = a + b,因此会导致不必要的内存使用,因为a 必须在其值更改之前复制一次。

【讨论】:

  • 我认为,如果我错了,请纠正我,只要涉及默认运算符实现,fundamental types(例如intfloat)将被视为任何其他类.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-11
  • 2023-01-08
  • 1970-01-01
相关资源
最近更新 更多