C++面向对象编程(2)

目录

一. 问题引入

二. 右值引用

1. lvalue/rvalue/prvalue/xvalue

1.1 表达式与对象的概念

1.2 左值与右值 

2. moving semantics

2.1 显示绑定

2.2 Move constructors

2.3 Move assignment operator

2.4 实例分析

// TODO

Quiz

REF


本章简单介绍下move语义的“来龙去脉”。

一. 问题引入

如下,先来看看拷贝赋值的一个过程。

Q1:上述过程看起来,

(1) “已知foo()返回的对象是临时的,要析构销毁的” ;

(2) f新开辟的地址空间“addr NewB”与foo()指向的地址空间大小和内容都是一样的。

那么,让f析构掉自己原来的地址空间“addr B”,然后f直接指向foo()的地址空间,foo()置为null不就可以了吗?这样还可以减少开辟和销毁内存空间的操作。过程如下:

Q2:当然基于Q1,我们还可以把delete f的地址空间addr B的任务交给foo(),即跟foo()的析构过程绑定在一起。这就是移动语义(moving semantics)。过程如下:

 到此,“移动语义”就被引入了。接下来的事情就是介绍C++是如何定义“移动语义”的。

二. 右值引用

为了实现移动语义,C++11引入了右值引用。

对于类型T,T&& 被称为对T的右值引用,T&被称为对T的左值引用。同样的移动语义(moving semantics)也有对应的移动构造和移动赋值,结构如下:

1. 移动构造

    类名 ( 类名 && )

2. 移动赋值

  类名 & 类名 :: operator= ( 类名 && )

同时,调用者可以使用std::move()库函数来实现移动语义的转换。

有了一个大概的概念,先来看看如下代码及运行结果。 

#include <iostream>using namespace std;void foo(int &&x)
{cout << "call && rvalue reference"<< endl;
}void foo(int &x)
{cout << "call & lvalue reference" << endl;
}int main() {int i = 1;int &lv = i; // 左值引用int &&rv = 2; // 右值引用foo(1); // call && rvalue referencefoo(i); // call & lvalue referencefoo(lv); // all & lvalue referencefoo(rv); // call & lvalue referencereturn 0;
}

运行结果及分析如下: 

// 因为foo(1);中的'1'是字面量,字面量为纯右值,这里不能用纯右值去初始化一个左值引用,所以会调用void foo(int &&x){}
call && rvalue reference// 因为foo(i);中的i是变量名,即是一个左值,所以会调用void foo(int &x){}
call & lvalue reference// 因为foo(lv);中的lv是一个左值引用,所以会调用void foo(int &x){}
call & lvalue reference// 虽然foo(rv);中的rv是一个右值引用,但是因为rv是一个变量名,所以也是一个左值,进而会调用void foo(int &x){} --从这里也可知道 右值引用其实也是一个左值
call & lvalue reference

接下来再来分别介绍下上面提到的4个概念:左值、右值、左值引用、右值引用。 

1. lvalue/rvalue/prvalue/xvalue

【《C++ Primer》(5th)4.1 基础】

 C++的表达式要不然是右值(rvalue,读作“are-value”),要不然就是左值(lvalue,读作“ell-value”)。

这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能。在C++语言中,二者的区别就没那么简单了。一个左值表达式的求值结果是一个对象或者一个函数,然而以常量对象为代表的某些左值实际上不能作为赋值语句的左侧运算对象。

1.1 表达式与对象的概念

Q:什么叫表达式?

A:《C++ Primer》里是这样解释的:“表达式(expression)最小的计算单元。一个表达式包含一个或多个运算对象,通常还包含一个或多个运算符。表达式求值会产生一个结果。例如,假设i和j是int对象,则i+j是一个表达式,它产生两个int值的和”。简单来说,表达式就是运算符和运算对象组成的序列,能指明一个计算且能够产生结果或作用。例如,“1+2”是表达式,“std::count << 1”也是表达式,但是 “std::count << 1;”则是一个语句。基本表达式(Primary Expression)有如下几种:

  • this
  • literals (e.g. 2 or "Hello, world")
  • id-expressions, including
    • suitably declared unqualified identifiers (e.g. n or cout),
    • suitably declared qualified identifiers (e.g. std::string::npos), and
    • identifiers to be declared in declarators
  • lambda-expressions
(since C++11)
  • fold-expressions
(since C++17)
  • requires-expressions
(since C++20)

这里顺带再补充下什么叫对象。

一般来讲,对象要有size,生命周期,类型,值等属性。如下实体就不是对象:引用,函数,枚举项,类型,类的非静态成员,模板,类或函数模板的特化,命名空间,形参包,和 this。

引用(reference)只是已有对象或函数的别名,编译器不必为其分配内存,因此也不存在引用数组,引用的引用,指向引用的指针等。 

1.2 左值与右值 

左值 (lvalue, left value),赋值符号左边的值。准确来说, 左值是表达式(不一定是赋值表达式)后依然存在的持久对象

右值 (rvalue, right value),右边的值,是指表达式结束后就不再存在的临时对象

而 C++11 中为了引入强大的右值引用,将右值的概念进行了进一步的划分,分为:纯右值、将亡值。

纯右值 (prvalue, pure rvalue),纯粹的右值,要么是纯粹的字面量,例如 10true; 要么是求值结果相当于字面量或匿名临时对象,例如 1+2。非引用返回的临时变量、运算表达式产生的临时变量、 原始字面量、Lambda 表达式都属于纯右值。

需要注意的是,字面量除了字符串字面量以外,均为纯右值。而字符串字面量是一个左值,类型为 const char 数组。

【《C++ Primer》(5th)4.1 基础】

到目前为止,已经有几种我们熟悉的运算符是要用到左值的。

· 赋值运算符需要一个(非常量)左值作为其左侧运算对象,得到的结果也仍然是一个左值。· 取地址符(参见2.3.2节,第47页)作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值。· 内置解引用运算符、下标运算符(参见2.3.2节,第48页;参见3.5.2节,第104页)、迭代器解引用运算符、string和vector的下标运算符(参见3.4.1节,第95页;参见3.2.3节,第83页;参见3.3.3节,第91页)的求值结果都是左值。

· 内置类型和迭代器的递增递减运算符(参见1.4.1节,第11页;参见3.4.1节,第96页)作用于左值运算对象,其前置版本(本书之前章节所用的形式)所得的结果也是左值。

· 内置类型和迭代器的递增递减运算符(参见1.4.1节,第11页;参见3.4.1节,第96页)作用于左值运算对象,其前置版本(本书之前章节所用的形式)所得的结果也是左值。

上面讲了一大堆,其实能记住的也就是下面几句话:

1. 任何有名字的表达式都是左值(枚举例外);

2. 字面量除了字符串字面量以外,均为纯右值。而字符串字面量是一个左值,类型为 const char 数组。

3. 右值的关键字在”临时“,例如,

(1) 求值结果相当于字面量或匿名临时对象,例如 1+2就是右值

(2) 非引用返回的临时变量、运算表达式产生的临时变量、 原始字面量、Lambda 表达式都属于纯右值。

value category是否是临时的是否占内存是否能被取地址是否能被赋值是否能用来初始化引用
lvalue(left/locator value)NYYY

Y

(例如

double d = 2.0;
double& rd = d;)  
rvalueprvalue(pure right value)
纯右值
YN例如 Foo f = f(); 这里的f()就是一个纯右值。NN

Y

(例如const int& r1 = 1; 1先被materialize,由prvalue转为xvalue)

xvalue(eXpiring value)

将亡值

YY右值不必占内存,并不是说不能占内存。例如f()这个函数是右值也是临时的,但是它必须占内存,因为需要使用f().X访问成员。NN仅能初始化const &
因为lvalue和xvalue都占内存,因此有的地方也把lvalue和xvalue统称为glvalue(generalized lvalue,泛化的左值)。
const int& r1 = 1; 1由prvalue转为xvalue

2. moving semantics

2.1 显示绑定

int &&r1 = 1;// 正确
int &&r2 = r1; // 错误,因为r1是左值

需要改成如下 

int&& r2 = std::move(r1); // 正确

用标准库函数std::move()来实现移动语义的自动转换。

2.2 Move constructors

Move constructors - cppreference.com

移动构造的语法规则如下:

可以使用标准库函数std::move()来实现自动转换。

// C++ reference 例子

#include <iomanip>
#include <iostream>
#include <string>
#include <utility>struct A
{std::string s;int k;A() : s("test"), k(-1) {}A(const A& o) : s(o.s), k(o.k) { std::cout << "move failed!\n"; }A(A&& o) noexcept :s(std::move(o.s)),       // explicit move of a member of class typek(std::exchange(o.k, 0)) // explicit move of a member of non-class type{}
};A f(A a)
{return a;
}struct B : A
{std::string s2;int n;// implicit move constructor B::(B&&)// calls A's move constructor// calls s2's move constructor// and makes a bitwise copy of n
};struct C : B
{~C() {} // destructor prevents implicit move constructor C::(C&&)
};struct D : B
{D() {}~D() {}           // destructor would prevent implicit move constructor D::(D&&)D(D&&) = default; // forces a move constructor anyway
};int main()
{std::cout << "Trying to move A\n";A a1 = f(A()); // return by value move-constructs the target// from the function parameterstd::cout << "Before move, a1.s = " << std::quoted(a1.s)<< " a1.k = " << a1.k << '\n';A a2 = std::move(a1); // move-constructs from xvaluestd::cout << "After move, a1.s = " << std::quoted(a1.s)<< " a1.k = " << a1.k << '\n';std::cout << "\nTrying to move B\n";B b1;std::cout << "Before move, b1.s = " << std::quoted(b1.s) << "\n";B b2 = std::move(b1); // calls implicit move constructorstd::cout << "After move, b1.s = " << std::quoted(b1.s) << "\n";std::cout << "\nTrying to move C\n";C c1;C c2 = std::move(c1); // calls copy constructorstd::cout << "\nTrying to move D\n";D d1;D d2 = std::move(d1);
}

运行结果如下 

2.3 Move assignment operator

Move assignment operator - cppreference.com

可以使用标准库函数std::move()来实现自动转换。

#include <iostream>
#include <string>
#include <utility>struct A
{std::string s;A() : s("test") {}A(const A& o) : s(o.s) { std::cout << "move failed!\n"; }A(A&& o) : s(std::move(o.s)) {}A& operator=(const A& other){s = other.s;std::cout << "copy assigned\n";return *this;}A& operator=(A&& other){s = std::move(other.s);std::cout << "move assigned\n";return *this;}
};A f(A a) { return a; }struct B : A
{std::string s2; int n;// implicit move assignment operator B& B::operator=(B&&)// calls A's move assignment operator// calls s2's move assignment operator// and makes a bitwise copy of n
};struct C : B
{~C() {} // destructor prevents implicit move assignment
};struct D : B
{D() {}~D() {} // destructor would prevent implicit move assignmentD& operator=(D&&) = default; // force a move assignment anyway 
};int main()
{A a1, a2;std::cout << "Trying to move-assign A from rvalue temporary\n";a1 = f(A()); // move-assignment from rvalue temporarystd::cout << "Trying to move-assign A from xvalue\n";a2 = std::move(a1); // move-assignment from xvaluestd::cout << "\nTrying to move-assign B\n";B b1, b2;std::cout << "Before move, b1.s = \"" << b1.s << "\"\n";b2 = std::move(b1); // calls implicit move assignmentstd::cout << "After move, b1.s = \"" << b1.s << "\"\n";std::cout << "\nTrying to move-assign C\n";C c1, c2;c2 = std::move(c1); // calls the copy assignment operatorstd::cout << "\nTrying to move-assign D\n";D d1, d2;d2 = std::move(d1);
}

运行结果如下:

2.4 实例分析

https://gcc.godbolt.org/z/1q9qcK9Pb

#include <iostream>
#include <cstring>
#include <utility>
using namespace std;class String {char * content;
public:String(const char * str = "") {if (str) {content = new char[strlen(str) + 1];strcpy(content, str);}cout << (void*)content << " : ctor\n";}String(const String &s) {content = new char[strlen(s.content) + 1];strcpy(content, s.content);cout << (void*)content << " : copy ctor\n";}String(String &&s) noexcept: content(std::exchange(s.content, nullptr)) {   cout << (void*)content << " : move ctor\n";cout << (bool)s.content << ", " << (void*)s.content << "\n";}String & operator=(const String &s) {if (this == &s)     return *this;if (!content || strlen(content) != strlen(s.content)) {delete[] content;content = new char[strlen(s.content) + 1];}strcpy(content, s.content);cout << (void*)content << " : copy assignment\n";return *this;}String & operator=(String && s) noexcept {std::swap(content, s.content);cout << (void*)content << " : move assignment\n";return *this;}~String() {cout << (void*)content << " : dtor\n";delete[] content;}
};class Msg {String content;unsigned from, to;
public:explicit Msg(const char * content, unsigned from, unsigned to) : content(content), from(from), to(to) {}
};int main() {Msg a("msg", 1, 2);Msg b = a;              // copy ctorMsg c = std::move(a);   // move ctorc = b;                  // copy assignc = std::move(b);       // move assignreturn 0;
}

运行结果

// TODO

Quiz

int& r = 1; // 错误,因为1是rvalue,是无法取地址的,因此这样写是非法的const int& r = 1; // 正确,因为1是先由prvalue转为xvalue,xvalue是占内存的。

再分析下如下“1 = i;”错误的原因

int i;i = 1;  // 正确1 = i;  // 错误

REF

1. 《Modern C++ Tutorial: C++ 11/14/17/20 On the Fly》

2. 《C++ Primer》(5th)

3. C++ reference - cppreference.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/59193.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day03_注释丶关键字丶标识符丶常量

​注释 注释就是使用人类的自然语言对代码的解释和说明。 代码本身和人类的自然语言相比&#xff0c;可读性肯定是要差一些&#xff0c;所以为了更快能够知道代码的含义、作用、需要注意地方&#xff0c;所有程序员都应该养成写注释的好习惯。 由于注释的内容是给程序员看的&…

《HelloGitHub》第 89 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 https://github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 …

融合正余弦和柯西变异的麻雀搜索算法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Error running ‘Tomcat 8.5.29‘ Address localhost:1099 is already in use

一、Error running ‘Tomcat 8.5.29’ Address localhost:1099 is already in use 原因&#xff1a;端口1099被占用了。 二、解决 2.1 解决方法一-结束该端口1099占用 //1-查看端口占用&#xff0c;根据端口号1099&#xff0c;获取PID(进程ID) netstat -ano | findstr "…

stackoverflow问题

Stack Overflow requires external JavaScript from another domain, which is blocked or failed to load. stackoverflow引用了谷歌中被屏ajax.googleapis.com的jquery.min.js文件。“https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js” 方案1.打开网站…

字节一面:你能讲一下跨域吗

前言 最近博主在字节面试中遇到这样一个面试题&#xff0c;这个问题也是前端面试的高频问题&#xff0c;作为一名前端开发工程师&#xff0c;我们日常开发中与后端联调时一定会遇到跨域的问题&#xff0c;只有处理好了跨域才能够与后端交互完成需求&#xff0c;所以深入学习跨域…

docker 04.更加重要的命令

之前的都是基础命令&#xff0c; 前台交互进程和后台守护进程&#xff1a; 重新进入容器&#xff1a; docker中的导入导出&#xff1a; docker中的拷贝到&#xff1a;

ubuntu学习(五)----读取文件以及光标的移动

1、读取文件函数原型介绍 ssize_t read(int fd,void*buf,size_t count) 参数说明&#xff1a; fd: 是文件描述符 buf:为读出数据的缓冲区&#xff1b; count: 为每次读取的字节数&#xff08;是请求读取的字节数&#xff0c;读上来的数据保存在缓冲区buf中&#xff0c;同时文…

ubuntu学习(四)----文件写入操作编程

1、write函数的详解 ssize_t write(int fd,const void*buf,size_t count); 参数说明&#xff1a; fd:是文件描述符&#xff08;write所对应的是写&#xff0c;即就是1&#xff09; buf:通常是一个字符串&#xff0c;需要写入的字符串 count&#xff1a;是每次写入的字节数…

小程序input的placeholder不垂直居中的问题解决

input的placeholder不垂直居中&#xff0c;input设置高度后&#xff0c;使用line-height只能使输入的文字垂直居中&#xff0c;但是placeholder不会居中&#xff0c;反而会偏上。 首先placeholder样式自定义 有两种方法&#xff0c;第一种行内样式&#xff1a; <input ty…

sql server 备份到网络共享

场景&#xff1a;sql server服务器A将数据库备份文件备份到服务器B 1&#xff09;服务器B创建共享目录 这里我将 D:\ProDbBak 共享&#xff0c;并且Everyone完全控制 2&#xff09;sql server服务器A能够访问服务器B共享目录&#xff0c;并且能完全控制 3&#xff09;修改服务…

c语言每日一练(12)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;暑假时三天之内必有一更&#xff0c;到了开学之后&#xff0c;将看学业情…

基于Axios完成前后端分离项目数据交互

一、安装Axios npm i axios -S 封装一个请求工具&#xff1a;request.js import axios from axios// 创建可一个新的axios对象 const request axios.create({baseURL: http://localhost:9090, // 后端的接口地址 ip:porttimeout: 30000 })// request 拦截器 // 可以自请求…

Git分布式版本控制系统与github

第四阶段提升 时 间&#xff1a;2023年8月29日 参加人&#xff1a;全班人员 内 容&#xff1a; Git分布式版本控制系统与github 目录 一、案例概述 二、版本控制系统 &#xff08;一&#xff09; 本地版本控制 &#xff08;二&#xff09;集中化的版本控制系统 &…

系统架构:软件工程

文章目录 资源知识点自顶向下与自底向上形式化方法结构化方法敏捷方法净室软件工程面向服务的方法面向对象的方法快速应用开发螺旋模型软件过程和活动开放式源码开发方法功用驱动开发方法统一过程模型RUP基于构件的软件开发UML 资源 信息系统开发方法 知识点 自顶向下与自底…

基于 Debian 12 的 Devuan GNU+Linux 5 为软件自由爱好者而生

导读Devuan 开发人员宣布发布 Devuan GNULinux 5.0 “代达罗斯 “发行版&#xff0c;它是 Debian GNU/Linux 操作系统的 100% 衍生版本&#xff0c;不包含 systemd 和相关组件。 Devuan GNULinux 5 基于最新的 Debian GNU/Linux 12 “书虫 “操作系统系列&#xff0c;采用长期支…

时序预测 | MATLAB实现基于QPSO-BiLSTM、PSO-BiLSTM和BiLSTM时间序列预测

时序预测 | MATLAB实现基于QPSO-BiLSTM、PSO-BiLSTM和BiLSTM时间序列预测 目录 时序预测 | MATLAB实现基于QPSO-BiLSTM、PSO-BiLSTM和BiLSTM时间序列预测效果一览基本描述程序设计参考资料 效果一览 基本描述 1.Matlab实现QPSO-BiLSTM、PSO-BiLSTM和BiLSTM神经网络时间序列预测…

java八股文面试[多线程]——Synchronized的底层实现原理

笔试&#xff1a;画出Synchronized 线程状态流转实现原理图 synchronized关键字解决的是多个线程之间访问资源的同步性&#xff0c;synchronized 翻译为中文的意思是同步&#xff0c;也称之为”同步锁“。 synchronized的作用是保证在同一时刻&#xff0c; 被修饰的代码块或方…

Day49|leetcode 121. 买卖股票的最佳时机、122.买卖股票的最佳时机II

leetcode 121. 买卖股票的最佳时机 题目链接&#xff1a;121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09; 视频链接&#xff1a;动态规划之 LeetCode&#xff1a;121.买卖股票的最佳时机1_哔哩哔哩_bilibili 题目概述 给定一个数组 &#xff0c;它的第 个元…