文章目录
- [第一章 C++23语言特性](#第一章 C++23语言特性)
-
- [1.5 简化隐式移动](#1.5 简化隐式移动)
- [1.6 auto(x) 和 auto{x}](#1.6 auto(x) 和 auto{x})
-
- [1.6.1 语法格式](#1.6.1 语法格式)
- [1.6.2 auto{}的使用](#1.6.2 auto{}的使用)
- [1.6.3 auto{x}总结](#1.6.3 auto{x}总结)
本文介绍C++23新特性之简化隐士移动和auto{}。
第一章 C++23语言特性
1.5 简化隐式移动
在 C++23 之前,编译器在 return x; 时自动执行 move(而不是 copy)是有严格条件的。如果条件不满足,必须手动调用move:
需要手动move的场景1:如果返回的类型和局部变量的类型不完全匹配。
cpp
struct Base
{
~Base()
{
}
};
struct Derived : public Base
{
~Derived()
{
}
};
// C++20 及以前
static std::unique_ptr<Base> create()
{
auto ptr = std::make_unique<Derived>();
// 错误!编译器不敢隐式 move,因为 Derived* 和 Base* 类型不同
// return ptr;
// 必须显式写:
return std::move(ptr);
}
void test()
{
auto bptr = create();
}
场景2:如果局部变量本身就是一个右值引用,之前的标准中认为它是一个"左值表达式",因此在返回时会尝试拷贝,除非再次move它。
cpp
// C++20 及以前
std::string forward_str(std::string&& s) {
// 错误!s 虽然是右值引用类型,但 s 变量本身是左值
// return s; // 尝试拷贝,如果 string 不可拷贝则报错
// 必须显式写:
return std::move(s);
}
C++23的解决方案,放宽原则。只要是 return 语句中的局部变量(包括参数),且该变量即将销毁,编译器就会默认将其视为右值 (xvalue),自动尝试移动构造,无需手动 std::move。
示例1:返回右值引用参数
cpp
std::unique_ptr<int> pass_through(std::unique_ptr<int>&& ptr) {
// C++20: 编译失败!
// ptr 有名字,所以是左值。unique_ptr 不可拷贝。
// 必须写 return std::move(ptr);
// C++23: 编译通过!✅
// 编译器知道 ptr 是参数,且马上要返回,自动 treat as rvalue。
return ptr;
}
示例2:工厂模式与多态
在工厂函数中,我们经常创建子类对象但返回基类指针(或智能指针)。
cpp
class Base {};
class Derived : public Base {};
std::unique_ptr<Base> create_instance() {
auto derived = std::make_unique<Derived>();
// 做一些针对 Derived 的初始化操作...
// derived->do_something();
// C++23 之前:必须 return std::move(derived);
// C++23:直接返回,自动处理类型转换 + 移动
return derived;
}
1.6 auto(x) 和 auto{x}
在C++23之前,编程时出现的两种问题:
问题1:lambda中的悬挂引用。在使用 Lambda 表达式或协程时,如果我们按引用捕获了一个临时对象,或者直接使用了参数的引用,可能会导致悬挂引用,这时我们需要一种方式强制拷贝一份数据保存下来。
问题2:泛型编程中的类型玻璃。泛型编程中的类型剥离在模板代码中,传入的参数可能是 const int&。如果我们想把这个值传给一个只接受右值的函数,或者想去掉 const 和引用属性得到原始类型 int,以前的需要使用decay手段去掉属性。
cpp
void func(const std::string& str) {
// 想得到 str 的一个非 const 副本
// 写法 1: 显式类型(不通用,如果 str 类型变了要改代码)
std::string copy1 = str;
// 写法 2: auto 变量(多占一行,打断了表达式的流畅性)
auto copy2 = str;
process(std::move(copy2));
// 写法 3: 复杂的 cast(难以阅读)
process(std::decay_t<decltype(str)>(str));
}
C++23中,直接使用auto(x),获得x的一个副本,并且这个副本是右值。
1.6.1 语法格式
auto{x} 和 auto(x) :复制一份x,复制的这份x后,auto{x}返回的是纯右值。得到的效果如下:
cpp
int val = 42;
const int& ref = val;
// copy 的类型是 int(去除了 const 和 &)
// copy 是一个右值
process(auto(ref));
1.6.2 auto{}的使用
下面代码中 take_owership函数接收一个右值。分别使用move() 和 auto{}方式实现。使用auto{data}实现的好处是,不破坏之前的资源所有权。
cpp
void take_owership(std::vector<int>&& v)
{
}
void test()
{
std::vector<int> data = { 1,2,3,4,5 };
// 错误!不能将左值传递给右值引用参数
// take_owership(data);
// 正确:使用 std::move 将左值转换为右值
take_owership(std::move(data));
// C++23做法
take_owership(auto{ data }); // 自动将 data 视为右值
for (int x : data)
{
std::cout << x << " ";
}
// 1 2 3 4 5
}
1.6.3 auto{x}总结
这是一种语法格式,既传递了右值,又不破坏之前的所有权,让代码变得更简洁。