管理类外资源的类必须定义拷贝控制成员。为了定义这些成员,我们必须确定此类型的拷贝语义。 类的行为可以定义为类值行为或类指针行为。
类值行为:类的行为像一个值,当我们拷贝一个像值的对象时,副本和原对象是完全独立的。以HasPtr类为实例(C++primer P453),为了实现类值行为,我们需要
(1)定义一个拷贝构造函数,完成string的拷贝,而不是拷贝指针
(2)定义一个折构函数来释放string
(3)定义一个拷贝赋值运算符来释放对象当前的string,并从右侧对象拷贝string.
#include <string>
class HasPtr {
public:
HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0) { }//构造函数
HasPtr(const HasPtr &hp) : ps(new std::string(*hp.ps)), i(hp.i) { }//ps指向的string,每个HasPtr对象都有自己的拷贝
//赋值运算符通常组合了折构函数和构造函数的操作。在处理时,需要考虑自赋值的情况。当编写一个赋值运算符时,一个好的模式是(1)先将右侧运算对象拷贝到一个局部临时对象。(2)然后摧毁左侧对象的成员,(3)最后将临时对象的数据拷贝到左侧运算对象的成员中
HasPtr& operator=(const HasPtr &hp) {
auto new_p = new std::string(*hp.ps);//构造函数操作 1
delete ps;//折构函数操作 2
//3
ps = new_p;
i = hp.i;
return *this;
}
~HasPtr() {
delete ps;
}
private:
std::string *ps;
int i;
};
类指针行为:行为像指针的类则共享状态。当我们拷贝一个类指针行为的对象时,副本和原对象使用相同的底层数据。设计此类对象可以使用引用计数,类似于shared_ptr的use_count.
引用计数工作的方式如下,计数器保存于动态内存当中,实现计数器的共享:
class HasPtr
{
public:
HasPtr(const string &s=string()):ps(new string(s)),i(0),use(new std::size_t(1)){}//构造函数,增加一个存于动态内存的计数器use
HasPtr(const HasPtr& p):ps(p.ps),i(p.i),use(p.use){++*use;}//拷贝构造函数需要递增计数器
HasPtr& operator=(const HasPtr& p);//拷贝赋值函数
~HasPtr();//折构函数
private:
string *ps;//string指针
int i;
std::size_t *use;//计数器,记录多少个对象共享*ps成员
};
HasPtr& HasPtr::operator=(const HasPtr& rhs)//拷贝赋值函数
{
++*rhs.use;//递增右侧运算对象的引用计数
if(--*use==0)//然后递减左侧运算对象引用计数,如果计数器为0,释放内存
{
delete ps;
delete use;
}
ps=rhs.ps;//拷贝rhs的数据到本对象
i=rhs.i;
use=rhs.use;
return *this;
}
HasPtr::~HasPtr()//递减左侧运算对象引用计数,如果计数器为0,释放内存
{
if(--*use==0)
{
delete ps;
delete use;
}
}
交换操作:
标准库的swap操作需要进行一次拷贝和两次赋值,效率较低。我们可以通过自己在类定义一个自定义版本的swap函数,来优化代码。可以将自定义版本的swap函数用于赋值运算符中,其能正确处理自赋值,自动是异常安全的。
//标准库swap操作
HasPtr temp=v1;//创建一个v1的值的临时副本
v1=v2;//将v2的值赋予v1
v2=temp;//将保存的v1的值赋予v2
#include <vector>
#include <algorithm>
#include <string>
#include <iostream>
class HasPtr
{
public:
friend void swap(HasPtr&, HasPtr&);
friend bool operator<(const HasPtr &lhs, const HasPtr &rhs);
HasPtr(const std::string &s = std::string())
: ps(new std::string(s)), i(0)
{ }//std::cout << " HasPtr(const std::string &s = std::string()) " << std::endl;}
HasPtr(const HasPtr &hp)
: ps(new std::string(*hp.ps)), i(hp.i)
{}//std::cout << " HasPtr(const HasPtr &hp) " << std::endl; }
HasPtr& operator=(HasPtr rhs)
{
swap(*this,rhs);
std::cout << " HasPtr& operator=(HasPtr rhs) " << std::endl;
return *this;
}
~HasPtr()
{
delete ps;
}
void show() const
{
std::cout << *ps << std::endl;
}
private:
std::string *ps;
int i;
};
void swap(HasPtr &lhs,HasPtr &rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i, rhs.i);
std::cout << "call swap(HasPtr &lhs,HasPtr &rhs) " << std::endl;
}
bool operator<(const HasPtr &lhs, const HasPtr &rhs)
{
return *lhs.ps < *rhs.ps;
}
int main(void)
{
HasPtr s{ "s" },a{"a"},c{"c"};
std::vector<HasPtr> vec{s,a,c};
std::sort(vec.begin(), vec.end());
for (auto const& elem : vec) elem.show();
return 0;
}
输出: