本文介绍C++14新特性,std::exchange 和 std::quoted。
文章目录
- [第二章 C++14 标准库特性](#第二章 C++14 标准库特性)
-
- [2.5 std::exchange](#2.5 std::exchange)
-
- [2.5.1 功能介绍](#2.5.1 功能介绍)
- [2.5.2 使用举例](#2.5.2 使用举例)
- [2.6 std::quoted](#2.6 std::quoted)
-
- [2.6.1 使用说明](#2.6.1 使用说明)
- [2.6.2 举例说明](#2.6.2 举例说明)
- [2.6.3 总结](#2.6.3 总结)
第二章 C++14 标准库特性
2.5 std::exchange
C++14中引入了std::exchange,包含在头文件中。
C++11引入了移动语义后,在编写类时需要添加移动构造和移动赋值运算符,完成资源的转移,比如下面例子中将其他对象的ptr 转移到当前对象中,C++11实现如下:
cpp
class Widget {
int* ptr_;
public:
// 移动构造函数
Widget(Widget&& other) {
ptr_ = other.ptr_; // 1. 获取资源
other.ptr_ = nullptr; // 2. 将源置空,防止析构时双重释放
}
};
这种实现比较经典,但是在C++14中引入了 std::exchange,将上边移动构造函数的共功能封装成了一个方法,可以让代码更简洁,下面开始介绍。
2.5.1 功能介绍
std::exchange 将一个新值赋给对象,并返回对象的旧值。大概的实现逻辑如下:
cpp
template<class T, class U = T>
T exchange(T& obj, U&& new_value) {
T old_value = std::move(obj); // 移动或拷贝旧值
obj = std::forward<U>(new_value); // 赋予新值,转发
return old_value; // 返回旧值
}
整体流程:分别传递进来一个新值和一个旧值。将新址资源转移给旧址,然后返回旧值。
2.5.2 使用举例
示例1:使用std::exchange完成移动构造函数。
cpp
class Buffer
{
public:
Buffer()
{}
// 移动构造
Buffer(Buffer && obj) noexcept
: m_pData(std::exchange(obj.m_pData,nullptr))
, m_size(std::exchange(obj.m_size,0))
{
// 总体实现逻辑:将
}
//
// 添加拷贝构造函数
Buffer(const Buffer& obj)
: m_size(obj.m_size)
{
// 注意:如果 m_pData 指向动态内存,这里需要进行深拷贝(Deep Copy)
// 否则两个对象指向同一块内存,析构时会崩溃
if (obj.m_pData)
{
delete m_pData;
m_pData = new char[m_size];
}
}
public:
char *m_pData = nullptr;
size_t m_size;
};
void test()
{
Buffer buf;
buf.m_size = 22;
cout << "buf.m_size = " << buf.m_size << endl;
Buffer buf2(std::move(buf));
cout << "buf.m_size = " << buf.m_size << endl;
cout << "buf2.m_size = " << buf2.m_size << endl;
/*
* buf.m_size = 22
buf.m_size = 0
buf2.m_size = 22
*/
}
示例2:状态重置与标志检查
当需要检查一个标志位时,并在检查后立即将其重置时,使用std::exchange非常方便。
cpp
void EventLoop::process() {
// 检查是否有新事件,如果有,处理它,并立即将 has_new_event_ 重置为 false
// 这一行代码完成了:读取 -> 判断 -> 重置
if (std::exchange(has_new_event_, false)) {
handle_events();
}
}
2.6 std::quoted
C++14之前,处理包含空格的字符串流(Stream)操作主要有两个痛点:
痛点1:读取待空格的字符串
使用cin>>s时,默认会以空格为分隔符,比如输入"hello world",流操作会把它切分成"Hello"和"world",导致数据错位。
痛点2:转义字符的丢失
当你把一个包含引号 " 或反斜杠 \ 的字符串写入文件,再读回来时,原始的结构往往会被破坏,因为这些特殊字符没有被正确转义(Escape)。
以前的笨办法: 你需要手动编写复杂的逻辑来检测引号、处理转义字符,或者使用 getline 配合自定义的分隔符解析逻辑。
std::quoted 的出现就是为了用一行代码解决"带引号和转义字符的字符串往返(Round-trip)"问题。
2.6.1 使用说明
std::quoted 是一个流操纵符(Stream Manipulator),它既可以用于输出流,也可以用于输入流。
函数原型
cpp
#include <iomanip>
// 用于输出
std::quoted(const String& s, char delim = '"', char escape = '\\');
// 用于输入
std::quoted(String& s, char delim = '"', char escape = '\\');
参数说明:
参数1:s 要处理的字符串。
参数2:delim 分隔符,默认为双引号"
参数3:escape,转义符,默认为反斜杠 \
函数作用:
输出时:自动在字符串两端加上双引号;如果字符串内部包含双引号或反斜杠,会自动在它们前面加上转义符,例如 " 或 \
输入时:期望输入流中的下一个token是被双引号包围的,自动去掉两端的双引号,自动处理转义字符。
2.6.2 举例说明
示例1:最经典的"空格截断"问题
cpp
// 不使用std::quote 处理带空格的字符串
void test2()
{
std::stringstream ss;
std::string out = "zhongguo beijing"s;
std::string in;
ss << out;
ss >> in;
std::cout << "原始: " << out << std::endl;
// 原始: zhongguo beijing
std::cout << "读取: " << in << std::endl;
// 读取: zhongguo
// beijings 在>> 操作符中被空格分隔符截断
}
// 使用 std::quote 处理带引号的字符串
void test()
{
std::stringstream ss;
std::string out = "zhongguo beijing"s;
std::string in;
// 写入是 "zhongguo beijing" 完整名字
ss << std::quoted(out);
// 读取时解析引号,正确读取完整名字
ss >> std::quoted(in);
std::cout << "原始: " << out << std::endl;
// 原始: zhongguo beijing
std::cout << "读取: " << in << std::endl;
// 读取: zhongguo beijing
}
示例2:处理特殊字符(JSON/CSV场景)
处理一个句子,包含了双引号" ",使用std::quote。
假设我们要保存一个包含引号的 句子:She said, "Hello, World!"
cpp
void test()
{
std::stringstream ss;
std::string original = R"(She said, "Hello, World!")";
// 1 写入,在写入时,quoted 会自动转义内部引号
ss << std::quoted(original);
cout << "流中的原始内容:" << ss.str() << std::endl;
// She said, \"Hello, World!\""
// 2 读取,quoted 会自动处理转义字符
std::string loaded;
ss >> std::quoted(loaded);
cout << "读取的内容:" << loaded << std::endl;
// 读取的内容:She said, "Hello, World!"
}
示例3:自定义分隔符(非标准CSV)
下面使用单引号' 作为定界符。
cpp
void test3()
{
std::stringstream ss;
std::string data = "O'Neil"; // 名字里带单引号
// 告诉 quoted 使用单引号作为边界,反斜杠作为转义
ss << std::quoted(data, '\'', '\\');
// 流中内容变为: 'O\'Neil'
std::cout << ss.str() << std::endl;
std::string loaded;
ss >> std::quoted(loaded, '\'', '\\');
cout << "读取的内容:" << loaded << std::endl;
// 读取的内容:O'Neil
}
2.6.3 总结
简化带空格,引号字符串的序列化和反序列化。
主要使用场景:读写配置文件、简单的 CSV 解析、JSON 字段生成、日志记录。