c++拷贝控制和资源管理
什么时候需要定义拷贝控制成员
通常,管理类外资源必须定义拷贝控制成员。这种类需要通过析构函数来释放对象所分配的资源。
一个类需要析构韩式,那么它几乎肯定也需要一个拷贝构造函数和一个拷贝赋值运算符。
拷贝语义
- 类的行为像一个值,当我们拷贝一个像值的对象时,副本和原对象时完全独立,改变副本不会影响原对象。
标准库容器和string类的行为像一个值。 - 行为像指针则共享状态,当我们拷贝一个这种类的对象时,副本和原对象使用相同的底层数据。改变副本也会改变原对象。
shared_ptr类提供类似指针的行为,IO类型和unique_ptr不允许拷贝或赋值,因此它们的行为既不像值也不像指针。
行为像值的类
class 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){}
//拷贝赋值运算符
HasPtr& opeartor=(const HasPtr& rhs)
{
auto newp = new std::string(*rhs.ps); //拷贝底层string
delete ps; //释放旧内存
ps = newp;
i = rhs.i;
return *this; //返回本对象
}
~HasPtr() {delete ps;}
private:
std::string *ps;
int i;
};
关键概念:赋值运算符
1.如果将一个对象赋值予它自身,赋值运算符必须能正确工作。
2.大多数赋值运算符组合了析构函数和拷贝构造函数的工作。
//这样编写赋值运算符时错误的!
HasPtr&
HasPtr::operator=(const HasPtr& rhs)
{
delete ps; //释放对象指向的string
//如果rhs和*this是同一对象,我们就将从已释放的内存中拷贝数据!
ps = new std::string(*rhs.ps);
i = rhs,i;
return *this;
}
如果rhs和本对象是同一个对象,delete ps会释放this和rhs指向的string。接下聊,当
我们new表达式中试图拷贝rhs.ps时,就会访问一个指向无效内存的指针,其行为和结果是未定义的。
定义行为像指针的类
class HasPtr
{
public:
HasPtr(const std::string &s = std::string())
:ps(new std::string(s)),i(0),use(new std::size_t(1)) {}
HasPtr(const HasPtr &p)
:ps(p.ps),i(p.i),use(p.use) { ++*use;}
HasPtr& operator=(const HasPtr& rhs)
{
++*rhs.use; //递增右侧运算符对象的引用计数
if(--*use == 0) //然后递减本对象的引用计数
{
delete ps; //如果没有其他用户
delete use; //释放本对象分配的成员
}
ps = rhs.ps; //将数据从rhs拷贝到本对象
i = rhs.i;
use = rhs.use;
return *this; //返回本对象
}
~HasPtr()
{
if(--*use == 0) //如果引用计数变为0
{
delete ps; //释放string内存
delete use; //释放计数器内存
}
}
private:
std::string *ps;
int i;
std::size_t *use;
};
赋值运算符与往常一样执行类似拷贝构造函数和析构函数的工作。即,它必须递增右侧运算对象的引用计数,并递减左侧
运算对象的引用计数,在必要时释放使用的内存。而且,赋值运算符必须处理自赋值。我们通过先递增rhs中的计数然后再递
减左侧运算对象中的计数来实现这一点。
练习题如下:
class TreeNode
{
public:
TreeNode()
:value(std::string()),count(new int(0)),left(nullptr),right(nullptr){}
TreeNode(const TreeNode& rhs)
:value(rhs.value),count(rhs.count),left(rhs.left),right(rhs.right) {++*count;}
TreeNode& operator=(const TreeNode& rhs)
{
++*rhs.count;
if(--*count == 0)
{
if(left) delete left;
if(right) delete right;
delete count;
}
value = rhs.value;
left = rhs.left;
right = rhs.right;
count = rhs.count;
}
~TreeNode()
{
if(--*count == 0)
{
if(left)
{
delete left;
left = nullptr;
}
if(right)
{
delete right;
right = nullptr;
}
delete count;
count = nullptr;
}
}
private:
std::string value;
int *count;
TreeNode *left;
TreeNode *right;
};
class BinStrTree
{
public:
BinStrTree():root(new TreeNode()){}
BinStrTree(const BinStrTree& bst):root(new TreeNode(*bst.root)){}
BinStrTree& operator=(const BinStrTree& rhs)
{
TreeNode *new_root = new TreeNode(*rhs.root);
delete root;
root = new_root;
return *this;
}
~BinStrTree(){delete root;}
private:
TreeNode *root;
};