C++拷贝控制与资源管理
参考《C++ Primer》P452 拷贝控制与资源管理
当一个类需要自定义析构函数,那么它几乎肯定也需要定义拷贝构造函数和拷贝复制运算符。 如下例子:
class HasPtr {
public:
HasPtr(const std::string &s = std::string()):
ps(new std::string(s)),i(0) {}
~HasPtr() {delete ps;}
//此时需要定义一个拷贝构造函数和一个拷贝赋值运算符
};
如果我们没有定义拷贝构造函数和拷贝赋值运算符,那么会调用类的合成拷贝构造函数和合成拷贝赋值运算符,这些函数简单拷贝指针成员,意味着多个HasPtr对象可能指向相同的内存。
HasPtr f(HasPtr hp)
{
HasPtr ret = hp; //拷贝给定的HasPtr
//处理ret
return ret; //ret和hp被销毁
}
当f返回时,hp和ret都被销毁,在这两个对象上都会调用HasPtr的析构函数,析构函数会调用delete ret和hp的指针成员,但这两个指针指向相同的内存块,则导致了指针被delete两次的错误。
定义行为像值的类
为了提供类值的行为,需要:
- 定义一个拷贝构造函数,完成string的拷贝,而不是指针拷贝;
- 定义一个析构函数来释放string;
- 定义一个拷贝复制运算符来释放对象当前的string,并从右侧运算对象拷贝string;
clas HasPtr {
public:
HasPtr(const std::string &s=std::string()):
ps(new std::string(s)),i(0) {}
HasPtr(const HasPtr &p):
ps(new std::string(*p.ps)),i(p.i) {} //拷贝string的值,而不是ps指针
HasPtr& operator=(const HasPtr&);
~HasPtr() {delete ps;}
private:
std::string *ps;
int i;
};
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
auto newp = string(*rhs.ps); //首先将右侧运算对象拷贝到局部临时变量中。
delete ps; //再删除左侧原始成员
ps = newp; //赋予新的成员值
i = rhs.i;
return *this; //返回对象本身
}
需要注意的是:
如果将一个对象赋予它自身,赋值运算符必须保证正确。
大多数赋值运算符组合了析构函数和拷贝构造函数的工作。
定义行为像指针的类
相比值行为类,指针行为类需要拷贝的是指针,会出现多个对象指向同一内存块的行为,则我们需要加入引用计数,计数器需要放置在动态内存中,当创建一个新的对象时,我们分配一个新的计数器,当拷贝或赋值对象时,我们拷贝指向计数器的指针。
class HasPtr{
public:
HasPtr(const string &s=string()):
ps(new string(s)),i(0),use(new size_t(1)){}
HasPtr(const HasPtr &p): //拷贝指针,计数器加1
ps(p.ps),i(p.i),use(p.use){++ *use}
HasPtr& operator=(const HasPtr &);
~HasPtr();
private:
string *ps;
int i;
size_t *use;
};
HasPtr::~Hasptr() {
if(-- *use==0) { //计数器减1,如果引用计数变为0,则释放string和计数器内存
delete ps;
delete use;
}
}
HasPtr& HasPtr::operator=(const HasPtr &rhs) {
++ *rhs.user; //右值对象计数器加1
if(--*use==0) { //左值对象计数器减1,如果计数器为0,则释放内存
delete ps;
delete use;
}
ps = rhs.ps;
i = rhs.i;
use = rhs.use;
return *this;
}