因为就算你类里面不显式写拷贝构造函数,那么在类内没有其他构造函数的时候,编译器也是会隐式地生成一个默认的拷贝构造函数的。又不是用 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对象指向同一个缓 冲区,这会导致析构的时候两次删除同一片区域的问题(这个问题又叫双杀问题)。
解决这个问题的方式有很多:
- 自己编写拷贝构造函数,然后在拷贝构造函数中创建新的buf_,不过拷贝构造函数的 编写需要考虑异常安全的问题,所以编写起来有一定的难度。
- 使用 shared_ptr 这样的智能指针,让所有的 Widget 对象共享一片 buf_,并 让 shared_ptr 的引用计数机制帮你智能的处理删除问题。
- 禁用拷贝构造函数和赋值操作符。如果你根本没有打算让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)函数三种。通常,我们说构造函数只特指普通构造函数。