拷贝构造/赋值函数的参数: 为什么我的拷贝函数报错?#

拷贝函数用 Widget(Widget const& other)operator=(Widget const& other).

本解答是很多人用 & 作为参数出错而提问, 我忍无可忍的结果.

为什么不是 Widget(Widget other)operator=(Widget other)?#

定义拷贝构造函数的目的是什么? 是为了描述 Widget 对象如何进行拷贝.

当用 Widget(Widget other) 时, 我们就是在说, Widget 先进行拷贝传参, 传入参数后进行拷贝. 拷贝传参也是在拷贝 Widget, 所以我们要为了拷贝传参这次拷贝进行拷贝传参, 传入参数后进行拷贝……

从前有座山, 山里有座庙, 庙里有个老和尚在讲故事, 讲的是从前有座山, 山里有座庙, 庙里有个老和尚在讲故事……

发生无限的递归.

../../_images/%E9%80%92%E5%BD%92%E6%8B%B7%E8%B4%9D.png

Widget(Widget other) 带来的递归问题. (以前做的图, 用语不太一样)#

为什么不是 Widget(Widget& other)operator=(Widget& other)?#

拷贝只是读取数据, 不会修改 other#

按照拷贝的逻辑, 我们是从 other 中读取数据, 它本身的数据不应该发生任何变化, 为了避免被修改, 应该使用 Widget const& 传递参数. 将 other 作为只读参数, 这称为输入参数.

Widget& 被意外修改#
 1class Widget {
 2 public:
 3  Widget(Widget& other) {
 4    /*...*/
 5    --other.value_;  // 意外被修改!
 6  }
 7
 8 private:
 9  int value_;
10};

Widget& 只能引用左值, 但很多情况需要用右值拷贝#

Widget& 报错#
 1class Widget {
 2 public:
 3  Widget(Widget& other);
 4};
 5
 6Widget function();
 7
 8int main() {
 9  Widget widget = function();  // 错误: function 返回了一个临时对象, 不能被引用而用于拷贝!
10}
Widget const& 正常运行#
 1class Widget {
 2 public:
 3  Widget(Widget const& other);
 4};
 5
 6Widget function();
 7
 8int main() {
 9  Widget widget = function();  // 正确!
10}

危险

以上内容是针对 C++98~C++23 的泛用解答, 随着版本变化, 很多细节会有差异.

事实上很多解答都存在这样的问题, 因为教学使用的 C++98 太过时了, VS 2017 默认版本是 C++14, gcc 11 和 clang 16 默认版本是 C++17.

这里引起的争议比较大, 所以免责声明一下.