现代C++特性深度探索:模板扩展、类增强、STL更新与Lambda表达式
四、可变参数模版
4.4 emplace系列接口
通过前面的学习,我们知道emplace_back和push_back的区别其实不是很大,真正的区别就是:
- emplace_bakc是一个可变参数模版,而push_back只是一个普通的函数
emplace_back可以传参数包进行构造,而push_back不能传参数包,要么是传插入的对象,或者是进行隐式类型转换



ok,当我们了解了这些,我们就来实现一个emplace_back:
4.5 emplace和emplace_back在list中的模拟实现
代码语言:javascript
AI代码解释
template<class ...Args>
void emplace_back(Args... args)
{
emplace(end(), forward<Args>(args)...);
}
template<class ...Args>
iterator emplace(iterator pos,Args&&... args)
{
Node* cur = pos._node;
Node* newnode = new Node(forward<Args>(args)...);
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
ok,当我们实现了emplace_back和emplace接口后,我们就要实现相应的可变模板参数版本的构造节点的代码:
代码语言:javascript
AI代码解释
template <class... Args>
ListNode(Args&&... args)
: _next(nullptr)
, _prev(nullptr)
, _data(std::forward<Args>(args)...)
{}
ok,这样改完之后,我们就可以使用emplace_back进行尾插操作。
但是,当我们加上emplace_back后,push_back就不能使用万能引用版本的尾插,为什么?
- 库中并没有把push_back写成泛型化,因为有了emplace_back就没有必要将push_back写成泛型化
- 要兼容以前的代码,我们只能写一个左值版本和一个右值版本的push_back代码

总结:有了emplace_back,就不需要这个万能引用版的push_back,直接使用左值版本和右值版本的push_back

emplace_back总体而言是更高效,推荐以后使用emplace系列替代insert和push系列
- 除此之外:emplace_back和push_back的用法是不能混着的~


五、C++11中类的新功能
5.1 默认的移动构造和移动赋值
原来的C++类中,有6个默认成员函数:构造、析构、拷贝构造、赋值重载、取地址重载、const取地址重载,最重要的是前4个,后2个用处不大
所谓默认成员函数,就是我们不显示写,编译器会默认生成一个。
C++11中新增了两个默认成员函数——
- 移动构造函数
- 移动赋值函数
通过前面的学习,我们知道,对于默认成员函数的学习,我们通常进行一下几点:
- 我们不显示写,编译器默认生成的行为是什么?满不满足我们的需求?如果不满足我们的需求,我们该怎么写?
ok,那我们先来看移动构造函数——
5.1.1 移动构造函数
如果你没有自己实现移动构造函数,并且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,(换句话来说:就是没有实现移动构造、析构函数、拷贝构造、拷贝赋值重载),那么编译器会自动生成一个默认移动构造(若实现了其中一个,编译器都不会生成默认移动构造)
默认生成的移动构造函数:
- 对于内置类型成员会执行逐成员按字节拷贝(完成值拷贝);
- 对于自定义类型成员,则需要看这个自定义类型成员中是否实现移动构造,如果实现了移动构造,则调用移动构造;如果没有实现移动构造,则调用拷贝构造
这就说明:自身需要深拷贝处理的类型,需要自己实现移动构造
通过上面,我们也能看出,移动构造函数试用于复合类型——
就比如,我们之前的MyQueue——
代码语言:javascript
AI代码解释
class MyQueue
{
std::stack _pushSt;
std::stack _popSt;
};
移动构造函数对于MyQueue就很有意义,MyQueue中没有写析构函数、拷贝构造、赋值重载以及移动构造,MyQueue中就会默认生成一个移动构造函数。
这个默认生成的移动构造函数:
- 对于内置类型成员,完成逐字节的拷贝;
- 对于自定义类型成员,会去调用栈中的移动构造,如果栈中没有实现移动构造,就去调用栈中的拷贝构造
移动构造和拷贝构造的逻辑相似!!!
代码语言:javascript
AI代码解释
namespace carrot
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
cout << "string(char* str)-构造" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s)
{
cout << "string(const string& s) -- 拷贝构造" << endl;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
// 移动构造
string(string&& s)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
if (this != &s)
{
_str[0] = '\0';
_size = 0;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
return *this;
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
~string()
{
//cout << "~string() -- 析构" << endl;
delete[] _str;
_str = nullptr;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
if (_str)
{
strcpy(tmp, _str);
delete[] _str;
}
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
};
}
class Person
{
public:
Person(const char* name="张三",int age=18)
:_name(name)
,_age(age)
{}
private:
bit::string _name;
int _age;
};
int main()
{
Person s1;//构造
Person s2 = s1;//拷贝构造
Person s3 = std::move(s2);//右值,移动构造
return 0;
}

ok,前两个还是比较容易理解的,我们直接看第三个,第三个就比较有意思——

运行结果——

那我们现在来验证一下,当自定义类型成员中没有移动构造函数,会不会调用自定义类型中的拷贝构造函数——

- 显示写移动构造函数的写法和拷贝构造的写法类似,拷贝构造会开新空间,而移动构造直接转移资源——
代码语言:javascript
AI代码解释
// 拷贝构造
string(const string& s)
{
cout << "string(const string& s) -- 拷贝构造" << endl;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
// 移动构造
string(string&& s)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}
注意:通过上面的代码,我们也能看出,不要轻易的对一个值进行move操作!!!
5.1.2 移动赋值函数
默认移动赋值和上面的移动构造完全类似
如果你没有自己实现移动构造函数,并且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,(换句话来说:就是没有实现移动构造、析构函数、拷贝构造、拷贝赋值重载),那么编译器会自动生成一个默认移动赋值(若实现了其中一个,编译器都不会生成默认移动赋值)
默认生成的移动赋值函数:
- 对于内置类型成员会执行逐成员按字节拷贝(完成值拷贝);
- 对于自定义类型成员,则需要看这个自定义类型成员中是否实现移动赋值,如果实现了移动赋值,则调用移动赋值;如果没有实现移动赋值,则调用赋值重载
ok,我们通过代码来看一下——
代码语言:javascript
AI代码解释
class Person
{
public:
Person(const char* name="张三",int age=18)
:_name(name)
,_age(age)
{}
private:
carrot::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s4("李四", 20);
s4 = std:: move(s2);
return 0;
}
运行一下——


那我们现在来验证一下,当自定义类型成员中没有移动赋值函数,会不会调用自定义类型中的拷贝构造函数——

ok,移动赋值函数这里还有一个特殊的地方——

我们通过调试来看一下——

移动赋值函数的写法和赋值重载的写法相似——
代码语言:javascript
AI代码解释
//赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
if (this != &s)
{
_str[0] = '\0';
_size = 0;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
return *this;
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
如果你自己提供了移动构造或者移动赋值,编译器就不会自动提供默认的移动构造或者移动赋值。
如果显示提供析构、拷贝构造、赋值重载中的任意一个,编译器就默认生不成移动构造和移动赋值了,就会去调用拷贝构造或者拷贝赋值(我们不写拷贝构造或者拷贝赋值,编译器会生成默认的拷贝构造),左值可以使用,右值也可以使用
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)