建设工程施工合同在哪个网站,wordpress底部导航插件,外贸开源网站,集团网站建设新闻移动语义和完美转发
移动语义
移动语义是 C11 引入的一项特性#xff0c;通过右值引用#xff08;Rvalue Reference#xff09;实现。它的目标是提高对于临时对象或即将销毁的对象的效率#xff0c;避免不必要的深拷贝#xff0c;而是在必要的时候将资源所有权从一个对象…移动语义和完美转发
移动语义
移动语义是 C11 引入的一项特性通过右值引用Rvalue Reference实现。它的目标是提高对于临时对象或即将销毁的对象的效率避免不必要的深拷贝而是在必要的时候将资源所有权从一个对象转移到另一个对象。
移动语义的关键在于对于右值引用的使用它使用 表示。在移动语义中有两个主要的概念移动构造函数和移动赋值运算符。
移动构造函数
移动构造函数是一种特殊的构造函数它允许将一个对象的资源例如动态分配的内存、文件句柄等从一个对象移动到另一个对象而不是进行深拷贝。
代码示例:
class MyClass {
public:// 移动构造函数MyClass(MyClass other) noexcept {// 移动资源的所有权resource other.resource;other.resource nullptr;}// 其他成员函数和构造函数省略private:int* resource; // 假设 resource 是需要移动的资源
};移动赋值运算符
移动赋值运算符用于将一个对象的资源移动到另一个对象类似于移动构造函数但是它在对象已经存在的情况下使用。
class MyClass {
public:// 移动赋值运算符MyClass operator(MyClass other) noexcept {if (this ! other) {// 释放当前对象的资源delete resource;// 移动资源的所有权resource other.resource;other.resource nullptr;}return *this;}// 其他成员函数和构造函数省略private:int* resource; // 假设 resource 是需要移动的资源
};move函数
move 是 C 标准库中定义的一个函数模板位于头文件 utility 中。它用于将一个左值转换为右值引用主要用于支持移动语义。move 并不实际进行移动操作它只是通过类型转换告诉编译器我们允许对这个左值进行移动操作。
move函数的实现代码:
namespace std {template typename Tconstexpr remove_reference_tT move(T arg) noexcept {return static_castremove_reference_tT(arg);}
}std::move 接受一个模板参数 T并返回一个 T 类型的右值引用。它实际上是通过 static_cast 进行类型转换的。在右值引用的特性中介绍到, T作为函数参数, 可以推到为左值引用
这里使用了 static_cast 将 T 转换为 remove_reference_tT。remove_reference_t 是一个模板元编程工具用于移除引用。这个转换告诉编译器我们希望将传入的左值引用 arg 转换为右值引用。
综合例子
#include iostream
#include stringclass MyString {
public:// 普通构造函数MyString(const char* data) : size(std::strlen(data)), str(new char[size 1]) {std::cout 普通构造函数 std::endl;strcpy_s(str, size 1, data);}// 移动构造函数MyString(MyString other) noexcept : size(other.size), str(other.str) {std::cout 移动构造函数 std::endl;other.size 0;other.str nullptr;}// 移动赋值运算符MyString operator(MyString other) noexcept {std::cout 移动赋值运算 std::endl;if (this ! other) {delete[] str;size other.size;str other.str;other.size 0;other.str nullptr;}return *this;}// 打印字符串void print() const {std::cout String: (str ? str : null) std::endl;}~MyString() {delete[] str;}private:size_t size;char* str;
};int main() {MyString a(Hello); // 普通构造函数a.print();MyString b(std::move(a)); // 移动构造函数a.print(); // nullb.print();// MyString c b; // error 移动构造函数, 右边应该是一个右值MyString c std::move(b); // 移动构造a MyString(World); // 普通构造函数 移动赋值运算符// MyString(World) 属于是一个用普通构造创建的一个右值, 通过赋值给aa.print();return 0;
}输出结果为:
普通构造函数
String: Hello
移动构造函数
String: null
String: Hello
移动构造函数
普通构造函数
移动赋值运算
String: WorldMyString b(std::move(a)); // 移动构造函数
a.print(); // nulla已经将资源转移给b了, 打印a输出null
完美转发
概述
完美转发是 C11 引入的一个重要特性它允许在模板中精确地保持函数参数的值类别左值还是右值并将它们传递到其他函数。完美转发通常用于实现泛型代码特别是在编写通用函数模板时非常有用。
完美转发的核心在于使用通用引用Universal Reference和引用折叠。通用引用的语法是 T其中 T 是模板参数。引用折叠是指在模板类型推导或类型声明的过程中两个引用无论是左值引用还是右值引用可能折叠成一个引用。
代码示例:
#include iostream
#include utility// 用于演示的一个简单处理函数
void process(int x) {std::cout Lvalue reference: x std::endl;
}void process(int x) {std::cout Rvalue reference: x std::endl;
}// 完美转发函数模板
template typename T
void forwarder(T arg) {// 在这里可以根据 arg 的值类别进行处理process(std::forwardT(arg));
}int main() {int x 42;forwarder(x); // 传递左值forwarder(123); // 传递右值return 0;
}输出为:
Lvalue reference: 42 Rvalue reference: 123
在这个例子中forwarder 是一个接受通用引用的函数模板它调用 process 函数并使用 std::forward 来完美转发参数。process 函数有两个重载版本分别接受左值引用和右值引用。
通过 forwarder(x) 调用时T 被推导为 int从而调用了接受左值引用的 process 版本。通过 forwarder(123) 调用时T 被推导为 int从而调用了接受右值引用的 process 版本。(详细说明可以看下面的forward函数)
在实际使用中完美转发通常结合模板参数包Template Parameter Pack和 std::forward 使用以处理任意数量和类型的参数。这使得编写通用的函数模板成为可能可以适应各种情况。
forward函数
这里从另一个角度说一下完美转发和forward函数
右值引用类型是独立于值的一个右值引用作为函数参数的形参时在函数内部转发该参数给内部其他函数时它就变成一个左值并不是原来的类型了。如果需要按照参数原来的类型转发到另一个函数可以使用C11提供的std::forward()函数该函数实现的功能称之为完美转发。
函数原型如下:
// 函数原型
template class T T forward (typename remove_referenceT::type t) noexcept;
template class T T forward (typename remove_referenceT::type t) noexcept;// 精简之后的样子
std::forwardT(t);重点:
当T为左值引用类型时t将被转换为T类型的左值当T不是左值引用类型时t将被转换为T类型的右值
代码示例:
#include iostream
using namespace std;templatetypename T
void printValue(T t)
{cout l-value: t endl;
}templatetypename T
void printValue(T t)
{cout r-value: t endl;
}templatetypename T
void testForward(T v)
{printValue(v);printValue(move(v));printValue(forwardT(v));cout endl;
}int main()
{testForward(520);int num 1314;testForward(num);testForward(forwardint(num));testForward(forwardint(num));testForward(forwardint(num));return 0;
}输出结果:
l-value: 520
r-value: 520
r-value: 520l-value: 1314
r-value: 1314
l-value: 1314l-value: 1314
r-value: 1314
r-value: 1314l-value: 1314
r-value: 1314
l-value: 1314l-value: 1314
r-value: 1314
r-value: 1314testForward(520);函数的形参为未定引用类型T实参为右值初始化后被推导为一个右值引用 printValue(v);已命名的右值v编译器会视为左值处理实参为左值 printValue(move(v));已命名的右值编译器会视为左值处理通过move又将其转换为右值实参为右值 printValue(forward(v));forward的模板参数为右值引用最终得到一个右值实参为右值 testForward(num);函数的形参为未定引用类型T实参为左值初始化后被推导为一个左值引用 printValue(v);实参为左值 printValue(move(v));通过move将左值转换为右值实参为右值 printValue(forward(v));forward的模板参数为左值引用最终得到一个左值引用实参为左值 testForward(forward(num));forward的模板类型为int最终会得到一个右值函数的形参为未定引用类型T被右值初始化后得到一个右值引用类型 printValue(v);已命名的右值v编译器会视为左值处理实参为左值 printValue(move(v));已命名的右值编译器会视为左值处理通过move又将其转换为右值实参为右值 printValue(forward(v));forward的模板参数为右值引用最终得到一个右值实参为右值 testForward(forwardint(num));forward的模板类型为int最终会得到一个左值函数的形参为未定引用类型T被左值初始化后得到一个左值引用类型 printValue(v);实参为左值 printValue(move(v));通过move将左值转换为右值实参为右值 printValue(forward(v));forward的模板参数为左值引用最终得到一个左值实参为左值 testForward(forwardint(num));forward的模板类型为int最终会得到一个右值函数的形参为未定引用类型T被右值初始化后得到一个右值引用类型 printValue(v);已命名的右值v编译器会视为左值处理实参为左值 printValue(move(v));已命名的右值编译器会视为左值处理通过move又将其转换为右值实参为右值 printValue(forward(v));forward的模板参数为右值引用最终得到一个右值实参为右值