C++拷贝构造函数与operator=()的现代简洁写法,如何“优雅”地拷贝构造与赋值
拷贝构造函数
传统写法
以实现一个简单的my::string字符串类为例my::string类的实现部分大致为
namespace my
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
string(); //构造函数
string(const char* str = ""); //带参构造函数
~string(); //析构函数
string(const string& s); //拷贝构造函数
string& operator=(string tmp); //赋值运算符重载
//此处省略大量成员函数
private:
char* _str = nullptr; //数据存储
size_t _size = 0; //size
size_t _capacity = 0; //开辟空间大小
};
}
//此处实现的string不涉及内存池/SSO优化
按照传统写法,我们需要手动开辟一块新的空间,进行手动拷贝,然后手动进行赋值。
string::string(const string& s)
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size + 1);
_size = s._size;
_capacity = s._capacity;
}
虽然代码逻辑不算复杂,但是这样写起来确实不够”优雅“,那么是否有更加简洁的写法呢?
现代简洁写法
在上面的传统写法中,我们相当于是手动构造了一个新的string对象,然后再依次将旧的对象赋值到新的对象中,那么是否有办法可以让程序自动完成构造新变量这一步呢?
注意到,我们在前面已经实现了string(const char* str = "")的方法,这意味着我们只需要一个字符串即可构造一个新对象。
构造新对象后,怎么让它的数据保存到当前调用方法的对象呢?
不禁让人想到了swap函数,它能够将两个变量的内部数据互换,因此我们可以利用它来进行交换数据。
于是得出以下代码:
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string::string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
在简洁写法中,我们使用带参构造,传入s._str字符串构造出对象tmp,顺利解决了传统写法中的手动构造问题。
使用手动实现的swap函数,在里面使用std::swap将数据一一交换,此时,调用方法的对象已经成功获得了利用tmp构造的目标对象的数据,而调用方法原有的空数据被交换给了tmp,随后string(const string& s)方法返回,tmp被调用析构函数释放了空间。
至此,我们就“优雅”地完成了拷贝构造
(可能大家会疑惑,为了一个拷贝构造专门实现一个swap函数,看起来并不划算,为什么说代码更简洁了呢?别急,下面来看看赋值运算符重载)
赋值运算符重载
传统写法
话不多说,直接展示传统写法
string& string::operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
memcpy(tmp, s._str, s._size + 1);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
依然如同上文的传统拷贝构造函数一样,存在相同的两个问题。
因此,我们可以迅速反应过来,将代码按照同样的思路进行优化。
现代简洁写法
按照上文相同思路优化的代码如下
void string::swap(string& s)//上文实现过的swap
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string& string::operator=(const string& s)
{
if (this != &s) {
string tmp(s);
swap(tmp);
}
return *this;
}
(上文实现过的swap函数在此处再次被调用,因此也解答了上文括号里提出的问题)
相信到这一步,一切都没有什么问题。
但是,我们能否更加偷懒,省去构造函数这行代码呢?
可以的!
我们都知道,在一个函数调用时,函数参数如果不是引用,那么就会拷贝一个新对象,参考下面的例子
class A
{
public:
int _data = 0;
};
void Test(A a)//a对象是用test拷贝构造的新对象
{
a._data = 1;//修改a与test无关,因此test里面_data还是0
}
int main()
{
A test;
Test(test);//使用test对象传参
return 0;
}
从上面的例子注意到,在函数参数列表位置是可以进行变量构造的,因此就有了更加简洁的写法:
将引用直接改为构造,省略一行代码
string& string::operator=(string tmp)//直接在这里调用拷贝构造
{
swap(tmp);
return *this;
}
在函数参数列表中就完成了拷贝构造,因此tmp已经是一个全新的对象,我们在代码块中就只需要交换两个对象,然后返回结果即可。
这样,我们就成功写出了"简洁"而又"优雅"的代码!
(内容如有错漏请在评论区or私信留言,看到后一定会仔细检查与修改!阿里嘎多!)
更多推荐



所有评论(0)