本文介绍 C++编程习语中的 拷贝-交换, 属于C++老司机需要掌握的一个技巧。
文中涉及到的全部相关代码地址:https://github.com/pengguoqing/samples_code/tree/master/c%2B%2B/idiom/copy_and_swap
一、拷贝交换习语要解决的问题
工作中当我们需要实现一个类用来管理某项资源时,除了构造函数外,通常还必须拷贝构造,拷贝赋值以及析构函数。当然,C++11后某些资源管理类需要只能移动不可拷贝或者赋值,比如 std::unique_ptr。拷贝构造和析构函数的实现都比较直接,拷贝赋值的实现就相对要复杂一些了。我在刚开始学习C++时,写的资源管理类的拷贝赋值大体如下:
CXRAII& operator=(const CXRAII &another)
{
if(this != &another)
{
delete[] m_dataptr;
m_dataptr = another.m_datasize ? new char[another.m_datasize]{} : nullptr;
m_datasize = another.m_datasize;
std::copy(another.m_dataptr, another.m_dataptr+m_datasize, m_dataptr);
}
return *this;
}
当时觉得没啥问题,后来经过 C++老司机的指导以及自己的实践发现这种实现有以下问题:
①自我赋值检测:防止类对象自我赋值时所管理的资源丢失,但是多数时候用不到,代码就显得不够简洁了。
②** 缺少异常保证**:假如 通过 new 申请堆内存失败了,此时 this 类就会管理一个野指针。程序崩溃就指日可待。
所以一个比较好一些的实现如下:
CXRAII& operator=(const CXRAII &another)
{
if(this != &another)
{
char* dataptr = another.m_datasize ? new char[another.m_datasize]{} : nullptr;
size_t datasize = another.m_datasize;
std::copy(another.m_dataptr, another.m_dataptr+datasize, dataptr);
delete[] m_dataptr;
m_dataptr = dataptr;
m_datasize = datasize;
}
return *this;
}
上面的实现可以保证 new 假如抛了异常后,this管理的资源仍然是完整没有被改变的。但是代码感觉还是不够简洁,所以拷贝-交换习语就应运而生了。
二、拷贝交换习语实现细节
首先为每个资源管理类提供一个互换成员变量的函数,本文的实现是先声明一个私有成员函数,再在命令空间内提供一个全局的成员互换函数,并将其声明为友元函数。据stack overflow 上的老司机将,所有的资源管理类都该提供一个全局的交换函数,和编译器会提供的5个默认函数同等重要。
private:
void Swap(CXRAII& another) noexcept
{
std::swap(this->m_dataptr, another.m_dataptr);
std::swap(this->m_datasize, another.m_datasize);
}
public:
friend void Swap(CXRAII& first, CXRAII& second);
void Swap(CXRAII& first, CXRAII& second)
{
first.Swap(second);
}
有了交换函数后,那么移动赋值和拷贝赋值的实现就可以写成:
CXRAII& operator=(const CXRAII& another)
{
CXRAII(another).Swap(*this);;
return *this;
}
//右值引用类型的变量是个左值
CXRAII& operator=(CXRAII&& another)
{
Swap(another);
return *this;
}
拷贝赋值中构造一个临时变量,再利用临时变量的析构释放 this 本来指向的资源。移动赋值中因为右值引用类型的变量是个左值,所以直接交换后,another 就可释放 this 本来指向的资源。我当时以为这就可了,后来看到一种最老的司机提供的方法可以合并这两种赋值,那就是:
CXRAII& operator=(CXRAII another) noexcept
{
cout << "operatro = " << endl;
Swap(another);
return *this;
}
我TM直呼 666。
三、测试
简单测试代码如下:
cout << "default fun test=========" << endl;
PX_CPLUSPLUS::CXRAII resman1(1);
resman1.Dump();
PX_CPLUSPLUS::CXRAII resman2(2, 'b');
resman2.Dump();
resman2 = resman1;
resman2 = GetRAII();
PX_CPLUSPLUS::Swap(resman1, resman2);
resman1.Dump();
resman2.Dump();
cout << "container test=========" << endl;
vector<PX_CPLUSPLUS::CXRAII> resmans(2);
cout << resmans.capacity() << endl;
resmans.emplace_back();
//resmans.push_back(PX_CPLUSPLUS::CXRAII());
cout << resmans.capacity() << endl;
cout << "test end =========" << endl;
输出结果为:

本文结束!!! 敬请期待下一篇。
本文介绍了C++编程中拷贝-交换(idiom)的概念,作为资源管理类的一种优化策略,它解决了拷贝赋值操作可能遇到的自我赋值检测和异常安全问题。文章详细展示了如何实现拷贝交换习语,包括提供私有的成员互换函数和全局友元交换函数,并通过示例代码演示了如何用拷贝交换来改进拷贝赋值和移动赋值的操作。
402

被折叠的 条评论
为什么被折叠?



