为什么不为std::vector重载operator+=()运算符?

30

我开始学习C++,因为我缺乏知识和经验,所以不明白为什么对于新手来说似乎如此简单的一件事情,如下述内容,不已经在STL中了。要将一个向量添加到另一个向量,您必须键入以下内容:

v1.insert(v1.end(), v2.begin(), v2.end());
我想知道在现实世界中,人们是否会重载+=运算符以使代码更加简洁,例如像这样的效果:
template <typename T>
void operator+=(std::vector<T> &v1, const std::vector<T> &v2) {
    v1.insert(v1.end(), v2.begin(), v2.end());
}

那么你可以这样做

v1 += v2;

我也将push_back设置为使用"+="将单个元素添加到末尾。 有没有一些原因不应该这样做或者在C++熟练的人中特别避免这些事情?


11
我猜测这个操作不是标准的,因为它有点棘手 - 在你的做法中,向量的值类型必须相同,而实际上你可以添加任何可转换为左侧值类型的容器(或范围)。而这正是insert已经实现的。此外,向量相加有两种可能的含义 - 像你在这里做的拼接,或者像valarray一样进行逐对相加,需要操作数大小相等。因此,这不是重载运算符的理想选择。 - Steve Jessop
12
更不用说,从标题上我就认为你的意思是对所有元素执行 v[i] += v[i] - dmckee --- ex-moderator kitten
1
在我看来,不将operator+=()视为成对加法的一个原因是向量的目的是包含元素。对元素进行操作超出了向量的设计/责任范围。基于这一点,根据最小特权原则,应该假定operator+=()是向量插入。 - Lukasz Wiklendt
2
@lukasz:显然你从未使用过MatLab(或Octave)。 - Ben Voigt
1
@Lukasz:五年后...这是“提升”的一个例子。你想将(+)运算符提升到向量容器中。能够做到这一点是非常好的。在Haskell中,zipwith是你要寻找的函数。它接受两个列表并使用一个函数将它们压缩在一起。你也可以在C++中编写zipwith。 - Samuel Danielson
显示剩余10条评论
4个回答

28

实际上,我希望在append()的形式中看到这个功能的重载。 operator+=有点模糊,您是指将每个向量的元素相加还是添加?

但是,正如我所说,我会欢迎:v1.append(v2); 它清晰简单,我不知道为什么没有。


1
@Mark:我同意,如果你想要这样的功能,那就自己去实现吧!template <typename L, typename R> void append(L& lhs, R const& rhs) { lhs.insert(lhs.end(), rhs.begin(), rhs.end()); }(使用一点 SFINAE 来限制 L 和 R 的容器类型)。 - Matthieu M.
@Matthieu,应该是const R& lhs而不是R const& rhs吧? - Lukasz Wiklendt
@Lukasz:不!那是一个可怕的英式用法!我更喜欢将限定符放在类型后面,因为这样更容易解释T const*是什么,const应用于它前面的内容(这里),所以最好保持一致! - Matthieu M.
@Lukasz:这两个是等价的。 - Ben Voigt
1
@MatthieuM。那个追加函数应该被称为append(a,b),而不是a.append(b),对吗? - Baruch
@baruch:是的,C++不支持像C#那样的扩展方法概念。 - Matthieu M.

8
我认为主要原因是+=的语义不明显。这里有一个含义,但是同样有效的含义是对于大小相等的向量中每个元素进行按元素加法。由于存在歧义,我假设他们决定依赖用户直接调用insert

另外,正如Steve Jessop在上面所说的那样,insert()函数更强大,因为它可以操作迭代器。你还需要其他不太有用的东西吗? - Kerrek SB
5
更强大并不代表更有用。通常来说,简单才是更好的选择。 - Ben Voigt

7
只有在不改变这些运算符的意义时才应该重载运算符。例如,如果两个对象之间没有数学定义,就不要为这些对象重载乘法运算符。如果存在数学对应关系,则通过重载运算符可以使类更方便使用,允许将方程表示为a*b+c的形式,而不是更冗长的(a.multiply(b)).add(c)。在使用加法和乘法运算符时,应当表达加法和乘法的意图。任何其他含义很可能会使其他人感到困惑。一些其他类型,如智能指针、迭代器、复数、向量和大数等,重载运算符是可以接受的(并且在我看来是首选)。
这源自C++的设计目标之一,即应该能够定义与内置类型一样易于使用的类。当然,你可以在类上定义操作符,这些操作符也不是数学概念。你可能希望重载==和!=运算符而不是编写isEqual方法,并且可能希望重载=,因为编译器的默认赋值运算符不是你想要的。
另一方面,重载运算符以执行意外操作,比如定义^将字符串翻译成日语,是模糊和危险的。你的同行程序员不会高兴地发现,看起来像异或比较实际上是完全不同的东西。解决方案是编写你的类,使编写清晰和易于维护的代码变得容易,无论是使用运算符重载还是避免它。
将两个向量相加太模糊,不值得定义一个运算符。正如其他人所展示的,许多人对这意味着什么有不同的想法,而对于字符串,将两个字符串相加意味着连接。在你的例子中,不太清楚你是否想在所有元素上进行分量相加,在末尾添加一个元素,还是连接两个向量。虽然这样做可能更简洁,但使用运算符重载创建自己的编程语言并不是最好的方法。
*是的,我知道Stroustrup重载了<<和>>来执行流操作而不是位移。但与算术和指针运算符相比,那些并没有经常使用,而且可以争论的是,现在每个人都知道如何使用cout,通常认为<<和>>是插入器和提取器运算符。(他最初尝试仅使用<和>进行输入和输出,但那些运算符的含义已经深深地烙印在每个人的脑海中,以至于代码难以阅读。)

2
我同意你关于显而易见的原则。然而,如果我给你一张纸,并说“把它加到纸堆里”,你会明白我的意思。而且,如果我们在一起进行配对编程,你刚刚写了代码来创建(并设置值)某个Foo对象,我说“现在你需要将它添加到Foo向量中”,我绝对不相信你会想到把向量中的每个Foo都增加一个刚刚创建的对象。你会调用push_back或insert函数,对吧? - Kate Gregory
如果你说“将这个Foo添加到Foos向量中”,是的,我会理解你的意思,并调用push_back,否则你应该说“将Foo添加到此向量中的每个Foo中”。如果有人说“将这两个向量相加”,我会认为他们想让我进行逐分量相加。如果连接向量是你想要的,我会期望你说类似于“将这个向量中的所有内容放在那个向量的末尾”。但问题就在这里,我觉得仅仅使用+=并不足以消除歧义。 - Jeff Linahan

4

除了其他人提到的这个语法不直观且容易出错之外,它还违反了一个好的设计原则,即将适用于各种容器的通用算法作为自由函数,将特定于容器的算法作为成员函数。大多数容器都遵循这个规则,除了std::string,Herb Sutter因其庞大的设计而受到了很多批评。


2
说到这个,当operator+()不满足交换律时(例如字符串连接),我从来不喜欢它。总觉得有些不对劲... - Nemo

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