通过与 Jira 对比,让您更全面了解 PingCode

  • 首页
  • 需求与产品管理
  • 项目管理
  • 测试与缺陷管理
  • 知识管理
  • 效能度量
        • 更多产品

          客户为中心的产品管理工具

          专业的软件研发项目管理工具

          简单易用的团队知识库管理

          可量化的研发效能度量工具

          测试用例维护与计划执行

          以团队为中心的协作沟通

          研发工作流自动化工具

          账号认证与安全管理工具

          Why PingCode
          为什么选择 PingCode ?

          6000+企业信赖之选,为研发团队降本增效

        • 行业解决方案
          先进制造(即将上线)
        • 解决方案1
        • 解决方案2
  • Jira替代方案

25人以下免费

目录

为什么c++中向map添加对象时不能使用拷贝构造函数

因为就算你类里面不显式写拷贝构造函数,那么在类内没有其他构造函数的时候,编译器也是会隐式地生成一个默认的拷贝构造函数的。又不是用 C++11 里 Person(const Person&) = delete 的语法明确禁止拷贝构造。

一、为什么c++中向map添加对象时不能使用拷贝构造函数

就算你类里面不显式写拷贝构造函数,那么在类内没有其他构造函数的时候,编译器也是会隐式地生成一个默认的拷贝构造函数的。又不是用 C++11 里 Person(const Person&) = delete 的语法明确禁止拷贝构造。所以光删掉拷贝构造函数的代码根本就不足以说明你的 Person 不能拷贝构造。你的猜测根本就不成立。

根据初学者写 C++ 时的一贯坏习惯,我猜可能是你拷贝构造没加常引用。果然,我把上面代码里拷贝构造函数改成 Person(Person&) ,就出现了跟你一样的编译错误。

为什么禁用拷贝构造函数

关于拷贝构造函数的禁用原因,我目前了解的主要是两个原因。名列前茅是浅拷贝问题,第二 个则是基类拷贝问题。

浅拷贝问题

编译器默认生成的构造函数,是memberwise拷贝^1,也就是逐个拷贝成员变量,对于 下面这个类的定义^2:

  class Widget { public: Widget(const std::string &name) : name_(name), buf_(new char[10]) {} ~Widget() { delete buf_; }   private: std::string name_; char *buf_; };

默认生成的拷贝构造函数,会直接拷贝buf_的值,导致两个Widget对象指向同一个缓 冲区,这会导致析构的时候两次删除同一片区域的问题(这个问题又叫双杀问题)。

解决这个问题的方式有很多:

  1. 自己编写拷贝构造函数,然后在拷贝构造函数中创建新的buf_,不过拷贝构造函数的 编写需要考虑异常安全的问题,所以编写起来有一定的难度。
  2. 使用 shared_ptr 这样的智能指针,让所有的 Widget 对象共享一片 buf_,并 让 shared_ptr 的引用计数机制帮你智能的处理删除问题。
  3. 禁用拷贝构造函数和赋值操作符。如果你根本没有打算让Widget支持拷贝,你完全可 以直接禁用这两操作,这样一来,前面提到的这些问题就都不是问题了。

基类拷贝构造问题

如果我们不去自己编写拷贝构造函数,编译器默认生成的版本会自动调用基类的拷贝构造 函数完成基类的拷贝:

  class Base { public: Base() { cout << “Base Default Constructor” << endl; } Base(const Base &) { cout << “Base Copy Constructor” << endl; } };   class Drived : public Base { public: Drived() { cout << “Drived Default Constructor” << endl; } };   int main(void) { Drived d1; Drived d2(d1); }

上面这段代码的输出如下:

  Base Default Constructor Drived Default Constructor   Base Copy Constructor // 自动调用了基类的拷贝构造函数

但是如果我们出于某种原因编写了,自己编写了拷贝构造函数(比如因为上文中提到的浅 拷贝问题),编译器不会帮我们安插基类的拷贝构造函数,它只会在必要的时候帮我们安 插基类的默认构造函数:

  class Base { public: Base() { cout << “Base Default Constructor” << endl; } Base(const Base &) { cout << “Base Copy Constructor” << endl; } };   class Drived : public Base { public: Drived() { cout << “Drived Default Constructor” << endl; } Drived(const Drived& d) { cout << “Drived Copy Constructor” << endl; } };   int main(void) { Drived d1; Drived d2(d1); }

上面这段代码的输出如下:

1 2 3 4 5 Base Default Constructor Drived Default Constructor   Base Default Constructor // 调用了基类的默认构造函数 Drived Copy Constructor

这当然不是我们想要看到的结果,为了能够得到正确的结果,我们需要自己手动调用基类 的对应版本拷贝基类对象。

  Drived(const Drived& d) : Base(d) { cout << “Drived Copy Constructor” << endl; }

这本来不是什么问题,只不过有些人编写拷贝构造函数的时候会忘记这一点,所以导致基 类子对象没有正常复制,造成很难察觉的BUG。所以为了一劳永逸的解决这些蛋疼的问题, 干脆就直接禁用拷贝构造和赋值操作符。

延伸阅读:

二、构造函数是什么

构造函数(Constructor)就是用来构造对象的函数。C++对象在创建的过程中会调用构造函数对类中成员进行初始化。

构造函数包含有普通构造函数、拷贝构造(Copy Constructor)函数以及C++11以后新增的移动构造(Move Constructor)函数三种。通常,我们说构造函数只特指普通构造函数。

相关文章