写在前面
总结一下C++类的拷贝构造与拷贝赋值部分, 从基本的示例入手, 一点点介绍拷贝控制与资源管理的一些内容.
参考Cppprimer(第七章:类, 第十三章:拷贝控制), effective C++(55)
类的拷贝赋值运算符
一般流程
- 先将右侧运算对象拷贝到一个局部临时对象中
- 拷贝完成后, 销毁左侧运算对象的现有成员(保证内存安全)
- 将数据从临时对象拷贝到左侧运算对象的成员中.
要点
-
自赋值的正确性: 保证赋值运算符将对象赋予其自身时能够正确工作.
要做到这一点, 一个好的方法是在销毁左侧运算对象资源之前, 先拷贝右侧运算对象.
-
大多数赋值运算符组合了析构函数和拷贝构造函数的工作.
实例: 行为像值的类
#include <iostream>
#include <string>
using namespace std;
class HasPtr {
friend void swap(HasPtr &, HasPtr &);
public:
HasPtr(const string &s = string()) : ps(new string(s)), i(0) {}
HasPtr(const HasPtr &p) : ps(new string(*p.ps)), i(p.i) {}
HasPtr &operator=(const HasPtr &rhs) {
auto newp = new string(*rhs.ps);
delete ps;
ps = newp;
i = rhs.i;
return *this;
}
string get() { return *ps; }
private:
string *ps;
int i;
};
inline void swap(HasPtr &lhs, HasPtr &rhs) {
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i, rhs.i);
cout << "swap by myself\n";
}
void t1() {
HasPtr a("hello");
HasPtr b;
b = a;
HasPtr c = a;
cout << a.get() << endl;
cout << b.get() << endl;
cout << c.get() << endl;
}
/* hello */
/* hello */
/* hello */
void t2() {
HasPtr a("a"), b("b");
cout << a.get() << endl;
cout << b.get() << endl;
swap(a, b);
cout << a.get() << endl;
cout << b.get() << endl;
}
int main(int argc, char *argv[]) {
/* t1(); */
t2();
return 0;
}
实例: 行为像指针的类
针对这种情况(例如string就是一个例子), 需要为其定义拷贝构造函数和拷贝赋值运算符, 此时拷贝的是指针成员, 而不是其指向的string对象.
标准库的实现: shared_ptr.
引用计数: 直接管理资源
其实就是shared_ptr
内部的一项内存资源管理技术, 工作流程:
-
除了初始化对象, 每个构造函数(除了拷贝构造函数)还要创建一个引用计数, 用于记录有多少对象与正在创建的对象共享状态. 第一次创建一个对象的时候, 只有一个对象共享状态, 所以引用计数器初始化为1.
-
拷贝构造函数不分配新的计数器, 而是拷贝给定对象的数据成员, 包括计数器. 调用拷贝构造函数递增共享的计数器, 表示智能指针所指对象的状态又被一个新用户所共享.
-
析构函数递减计数器, 指出共享状态的用户少了一个, 如果计数器变为零, 析构函数开始释放状态.
-
拷贝赋值运算符递增右侧运算对象的计数器, 递减左侧运算对象的计数器.
如果左侧运算对象的计数器变为0, 意味着它的共享状态没有用户了, 拷贝赋值运算符就必须销毁状态了.
注意, 计数器的位置不能直接存储在行为像指针的类的实例化对象中, 成为对象的成员. 因为引用计数更新的不同步性不能在每一个所指对象中传递, 所以需要动态创建保存计数器的内存, 使得副本和原始对象都有相同的计数器.
示例代码
#include <cstddef>
#include <iostream>
#include <string>
using namespace std;
class HasPtr {
public:
HasPtr(const string &s = string())
: ps(new string(s)), i(0), use(new size_t(1)) {}
HasPtr(const HasPtr &p) : ps(p.ps), i(p.i), use(p.use) {
++*use;
cout << "after copy ctor, use: " << *use << endl;
}
HasPtr &operator=(const HasPtr &);
HasPtr &operator=(const string &);
string &operator*() { return *ps; }
~HasPtr();
int get_use_cnt() { return *use; }
void print() {
cout << "*ps: " << *ps << ", i: " << i << ", *use: " << *use << endl;
}
private:
string *ps;
int i;
size_t *use; // 记录有多少对象共享*ps的成员
};
HasPtr::~HasPtr() {
cout << "before dtor, use: " << *this->use << endl;
if (--*use == 0) {
delete ps;
delete use;
use = nullptr;
}
if (use)
cout << "after dtor, use: " << *this->use << endl;
else
cout << "use deleted..\n";
}
HasPtr &HasPtr::operator=(const HasPtr &rhs) {
cout << "before copy assignment, this->use: " << *this->use << endl;
cout << "before copy assignment, rhs.use: " << *rhs.use << endl;
++*rhs.use;
if (--*use == 0) {
delete ps;
delete use;
use = nullptr;
}
ps = rhs.ps;
i = rhs.i;
use = rhs.use; // 这一步将右对象的引用计数赋值给左对象,
// 使得两个对象共享同一份引用计数
cout << "after copy assignment, this->use: " << *this->use << endl;
cout << "after copy assignment, rhs.use: " << *rhs.use << endl;
return *this;
}
HasPtr &HasPtr::operator=(const string &rhs) {
*ps = rhs;
return *this;
}
void t1() {
//
cout << "HasPtr h(\"abc\")\n";
HasPtr h("abc"), h2;
cout << "h: ";
h.print();
cout << "h2: ";
h2.print();
/* cout << "\nHasPtr h2 = h;\n"; */
/* HasPtr h2 = h; */
/* cout << "h: "; */
/* h.print(); */
/* cout << "h2: "; */
/* h2.print(); */
/* cout << "\nh = \"cde\"\n"; */
/* h = "cde"; */
/* cout << "h: "; */
/* h.print(); */
/* cout << "h2: "; */
/* h2.print(); */
cout << "\nh = h2;\n";
h2 = h;
cout << "h: ";
h.print();
/* cout << h.get_use_cnt() << endl; */
cout << "h2: ";
h2.print();
/* cout << h2.get_use_cnt() << endl; */
}
/* HasPtr h("abc") */
/* h: *ps: abc, i: 0, *use: 1 */
/* */
/* HasPtr h2 = h; */
/* h: *ps: abc, i: 0, *use: 2 */
/* h2: *ps: abc, i: 0, *use: 2 */
/* */
/* h = "cde" */
/* h: *ps: cde, i: 0, *use: 2 */
/* h2: *ps: cde, i: 0, *use: 2 */
int main(int argc, char *argv[]) {
t1();
return 0;
}