首页 > 编程语言 >C++左值右值完美转发转移

C++左值右值完美转发转移

时间:2024-03-09 23:11:50浏览次数:33  
标签:std String 右值 左值 C++ && arg

左值(Lvalue)与右值(Rvalue)

英文含义:

  • 左值(Lvalue)Locator value,意味着它指向一个具体的内存位置。

  • 右值(Rvalue)Read value,指的是可以读取的数据,但不一定指向一个固定的内存位置。

定义

  • 左值:指的是一个持久的内存地址。左值可以出现在赋值操作的左侧或右侧。例如,变量、数组的元素、对对象成员的引用等都是左值。
  • 右值:通常是临时的、不能有多个引用的值,它们不指向持久的内存地址。右值可以出现在赋值操作的右侧,但不能出现在左侧。字面量(如42、3.14)、临时对象、以及返回临时对象的表达式等都是右值。

完美转发(Perfect Forwarding)

完美转发是C++11引入的一个概念,其目的是允许函数模板将参数以原来的左值或右值的形式转发到其他函数。这是通过引用折叠规则std::forward函数实现的。完美转发的一个关键应用场景是模板函数中,我们希望将接收到的参数以完全相同的形式(保持其左值或右值性质)传递给另一个函数时使用。

引用折叠规则

在模板函数或类中,当一个引用的引用被形成时,它们会折叠成单一的引用

  • T& & ,T& &&, T&& & 都会被折叠为 T&
  • T&& && 会被折叠为T&&

示例

  • wrapper(lv)被调用时,lv是一个左值,因此模板参数T被推断为int&(左值引用)。由于引用折叠规则,T&&折叠为int&。因此,std::forward<T>(arg)arg作为左值引用转发给process函数,调用process(int& i)版本。
  • wrapper(20)被调用时,20是一个右值,因此模板参数T被推断为int。由于T是一个非引用类型,T&&就直接是int&&(右值引用)。因此,std::forward<T>(arg)arg作为右值引用转发给process函数,调用process(int&& i)版本。
#include <iostream>
#include <utility> // std::forward

// 分别处理左值和右值
void process(int& i) {
    std::cout << "Process left value: " << i << std::endl;
}
void process(int&& i) {
    std::cout << "Process right value: " << i << std::endl;
}

// 完美转发的模板函数
template<typename T>
void wrapper(T&& arg) {
    // 使用std::forward来完美转发arg
    process(std::forward<T>(arg)); 
}

int main() {
    int lv = 10; // 左值
    wrapper(lv); // arg被推断为左值引用,因为lv是一个左值
    wrapper(20); // 20是右值,arg被推断为右值引用
    return 0;
}
/*
Process left value: 10
Process right value: 20
*/

转移(Move)

转移是指将一个对象的资源(如动态内存)从一个实例转移到另一个实例,而不是复制资源。这通常通过移动构造函数和移动赋值操作符实现,它们接受一个右值引用(Rvalue reference)作为参数。移动语义允许资源的高效转移,避免了不必要的复制,特别是对于大型对象或资源密集型对象。

使用std::move方法可以将左值转换为右值。使用这个函数并不能移动任何东西,而是和移动构造函数一样都具有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。

从实现上讲,std::move基本等同于一个类型转换:static_cast<T&&>(lvalue);,函数原型如下:

template<class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT
{	// forward _Arg as movable
    return (static_cast<remove_reference_t<_Ty>&&>(_Arg));
}

联系

  • 完美转发和移动语义都紧密依赖于左值和右值的概念。完美转发用于保持参数的左值或右值性质不变,而移动语义则是利用右值(通常是即将销毁的临时对象)来优化资源的管理。
  • 移动语义是完美转发常见的一个应用场景。当使用完美转发将函数参数传递给另一个函数时,如果该参数是一个临时对象(右值),则可以利用移动构造函数或移动赋值操作符,从而提高效率。
  • 右值的概念是转移语义的基础。只有右值(临时对象或显式标记为右值的对象)才能被移动,以此来优化资源的使用和提高程序的运行效率。

示例

以下是一个简单示例,其中包含一个自定义的String类,这个类通过实现移动构造函数和移动赋值操作符来优化内存资源管理。

同时,代码使用完美转发的函数模板,它可以根据传入参数的类型(左值或右值)来决定是否使用移动语义。

#include <iostream>
#include <cstring>
#include <utility> // std::move and std::forward

class String {
private:
    char* data;
    size_t length;

    void freeData() {
        delete[] data;
    }

public:
    // 构造函数
    String(const char* p = "") : length(strlen(p)), data(new char[length + 1]) {
        std::copy(p, p + length + 1, data);
        std::cout << "Constructed\n";
    }

    // 析构函数
    ~String() {
        freeData();
    }

    // 拷贝构造函数
    String(const String& other) : length(other.length), data(new char[length + 1]) {
        std::copy(other.data, other.data + length + 1, data);
        std::cout << "Copied\n";
    }

    // 移动构造函数
    String(String&& other) noexcept : data(other.data), length(other.length) {
        other.data = nullptr;
        other.length = 0;
        std::cout << "Moved\n";
    }

    // 移动语义的赋值运算符
    String& operator=(String&& other) noexcept {
        if (this != &other) {
            freeData();
            data = other.data;
            length = other.length;
            other.data = nullptr;
            other.length = 0;
            std::cout << "Move Assigned\n";
        }
        return *this;
    }

    void print() const {
        if (data) {
            std::cout << data << std::endl;
        }
    }
};

// 完美转发示例
template<typename T>
void relay(T&& arg) {
    // 使用 完全转发 来保持'arg'的左值/右值性质。
    String temp(std::forward<T>(arg));
    temp.print();
}

int main() {
    String s1("Hello");
    String s2(std::move(s1)); // 调用移动构造函数

    s1 = String("World"); // 移动语义赋值调用

    String s3("Goodbye");
    relay(s3); // 左值被传递
    relay(String("Hello World")); // 右值被传递

    return 0;
}
/*
Constructed  
Moved        
Constructed  
Move Assigned
Constructed  
Copied       
Goodbye      
Constructed
Moved
Hello World
*/

程序输出:

Constructed  
Moved        
Constructed  
Move Assigned
Constructed  
Copied       
Goodbye      
Constructed
Moved
Hello World

String类包含了移动构造函数和移动赋值操作符,当与右值交互时,可以有效地转移资源而不是进行复制。这样,当有一个临时的String对象时(例如在main函数中通过String("World")创建的临时对象),这个对象的资源可以被转移到另一个对象中而不需要额外的复制开销。

参考

标签:std,String,右值,左值,C++,&&,arg
From: https://www.cnblogs.com/Az1r/p/18063558

相关文章

  • [c++] c++ 中的左右值
    c++中的左右值前言:最近又需要再次复习一下关于c++中lvaue和rvalue相关的知识。左值和右值简单看inta=1;这条语句,对于左边的a,我们是可以对其取地址的,而对于右边的1来说,我们无法对其去地址。对于能够取地址的a,位于=左边,我们就将其称之为左值,对于像1这样的......
  • C++ 17新特性
    C++17是C++语言的一个重要版本,引入了许多新特性和改进,以提高开发效率和代码质量。以下是一些常用的C++17新特性:结构化绑定(StructuredBindings):允许以声明式语法将复合类型(如元组、数组、结构体)中的成员绑定到变量上,从而简化代码并提高可读性。#include<tuple>std::tup......
  • C++ Qt开发:QNetworkInterface网络接口组件
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QNetworkInterface组件实现查询详细的网络接口参数。在Qt网络编程中,QNetworkInterface是......
  • C++ Qt开发:QHostInfo主机地址查询组件
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QHostInfo组件实现对主机地址查询功能。在Qt网络编程中,QHostInfo是一个强大而灵活的组件......
  • 【C/C++】四舍五入、向上取整、向下取整
    #include<stdio.h>#include<stdint.h>doublecustom_pow(doublebase,intexponent){doubleresult=1.0;if(exponent>=0){for(inti=0;i<exponent;i++){result*=base;}}else{......
  • C++习题
    打印菱形 #include<iostream>usingnamespacestd;intmain(){cout<<"*"<<endl;cout<<"***"<<endl;cout<<"*"<<endl;return0;} 进制转换#include<iostream>#include<iomanip&g......
  • c++小知识(2)
    一、引用和指针的区别:指针:从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。引用:是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对......
  • C++ Qt开发:QFileSystemWatcher文件监视组件
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QFileSystemWatcher组件实现对文件或目录的监视功能。QFileSystemWatcher是Qt框架中......
  • C/C++结构体最详细的讲解
    转载自知乎:https://zhuanlan.zhihu.com/p/6117720311.定义结构体法一(推荐,写法简单)structStudent{stringm_Name;intm_Age;Student()=default;Student(stringname,intage):m_Name(name),age(m_Age){}};一般定义结构体和类时,如果不写关于构造函数的任何东西,结构......
  • c++多线程
    1.线程库的使用创建进程#include<iostream>#include<thread>//线程库头文件usingnamespacestd;voidthread_1(){cout<<"子线程1"<<endl;}intthread_2(intx){cout<<"子线程2"<<endl;returnx;}intm......