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

news2024/12/25 12:24:40

目录

一. 问题引入

二. 右值引用

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 reference
    foo(i); // call & lvalue reference
    foo(lv); // all & lvalue reference
    foo(rv); // call & lvalue reference
    
    return 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 type
        k(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 parameter
 
    std::cout << "Before move, a1.s = " << std::quoted(a1.s)
        << " a1.k = " << a1.k << '\n';
 
    A a2 = std::move(a1); // move-constructs from xvalue
    std::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 constructor
    std::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 constructor
 
    std::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 assignment
    D& 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 temporary
    std::cout << "Trying to move-assign A from xvalue\n";
    a2 = std::move(a1); // move-assignment from xvalue
 
    std::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 assignment
    std::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 operator
 
    std::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 ctor
    Msg c = std::move(a);   // move ctor
    c = b;                  // copy assign
    c = std::move(b);       // move assign
    return 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.coloradmin.cn/o/948623.html

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

相关文章

数据通信——传输层TCP(可靠传输原理的停止等待协议)

引言 基于上次的基本特性中的可靠传输特性&#xff0c;我们对其进行详细的分析。 对了&#xff0c;在讲解TCP前&#xff0c;先对缓存进行一个简单的描述。我们都听说过缓存这个东西&#xff0c;我们发来的数据&#xff0c;会暂时写入到缓存中进行预处理&#xff0c;随后从缓存中…

OpenAI发布ChatGPT企业级版本

本周一&#xff08;2023年8月28日&#xff09;OpenAI 推出了 ChatGPT Enterprise&#xff0c;这是它在 4 月份推出的以业务为中心的订阅服务。该公司表示&#xff0c;根据新计划&#xff0c;不会使用任何业务数据或对话来训练其人工智能模型。 “我们的模型不会从你的使用情况中…

最适合家用的洗地机哪个牌子好?好用洗地机推荐

在如今的快节奏下&#xff0c;许多人没有太多的时间去完成家务清洁这一大问题&#xff0c;但是随着洗地机的出现&#xff0c;很多小伙伴们都发现了这个智能清洁家电在我们的生活中频繁出现&#xff0c;比如最近比较火爆的智能洗地机&#xff0c;结合了吸拖洗为一体的高效清洁与…

Bigemap 在水土生态环境行业中的应用

工具 Bigemap gis office地图软件 BIGEMAP GIS Office-全能版 Bigemap APP_卫星地图APP_高清卫星地图APP 使用场景&#xff1a; 1. 土地利用占地管理&#xff1a; 核对数据&#xff0c;查看企业的实际占地是否超出宗地&#xff0c;污染面积。不方便现场勘测的地方&#xf…

Python实操GetOpenFilename打开文件选择框获取工作表信息

import xlwings as xw xlapp xw.App(visibleTrue, add_bookFalse) a xlapp.api.GetOpenFilename(Excel Files (*.xl*),*.xl*, 0, 0, 0, True) print(a[0]) slist [] for i in a:wk xw.Book(i)for s in wk.sheets:s_dict {}s_dict[工作簿名字] wk.names_dict[工作表名字] …

K 次取反后最大化的数组和【贪心算法】

1005 . K 次取反后最大化的数组和 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后&#xff0c;返回数组 可能…

信看课堂笔记—LDO和DC-DC电路打PK

LDO&#xff08;low dropout voltage regulator&#xff0c;低压差线性稳压器&#xff09;和DC-DC(Direct current-Direct current converter&#xff0c;直流电压转直流电压转换器)电源是非常常见的电源电路&#xff0c;LDO 出来的比较早&#xff0c;像老戏骨一样&#xff0c;…

热烈祝贺天光长寿食品成功入选航天系统采购供应商库

经过航天系统采购平台的严审&#xff0c;单州天光长寿食品&#xff08;单县&#xff09;有限公司成功入选中国航天系统采购供应商库。航天系统采购平台是航天系统内企业采购专用平台&#xff0c;服务航天全球范围千亿采购需求&#xff0c;目前&#xff0c;已有华为、三一重工、…

邀您试用|一键生成企业数字人,深兰科技硅基大脑SaaS平台使用指南

深兰科技“硅基大脑SaaS平台”是在深兰科技硅基知识大模型的基础上进行领域知识和个性化人设等高维数据的定制化训练&#xff0c;并以其零代码、一键生成、低成本等特点&#xff0c;实现从形象到知识体系的全面自定义&#xff0c;满足企业在智能客服、数字员工和行业专家等多个…

学习乐趣无限:学乐多光屏P90助力儿童智能学习新纪元

在这个变革的浪潮中&#xff0c;学乐多光屏P90以其卓越的功能和深刻的教育理念&#xff0c;成为了智能儿童学习领域的引领者&#xff0c;为孩子们开启了全新的学习体验。 融合创新技术&#xff0c;引领学习变革 学乐多光屏P90凭借其独特的触摸和投影光学技术&#xff0c;为儿…

K8S访问控制------认证(authentication )、授权(authorization )、准入控制(admission control )体系

一、账号分类 在K8S体系中有两种账号类型:User accounts(用户账号),即针对human user的;Service accounts(服务账号),即针对pod的。这两种账号都可以访问 API server,都需要经历认证、授权、准入控制等步骤,相关逻辑图如下所示: 二、authentication (认证) 在…

1+X智慧安防系统实施与运维技能等级证产教融合基地建设方案

一、系统概述 1X智慧安防系统实施与运维技能等级证产教融合体系统融合了产业需求、教育培训和技能认证&#xff0c;通过课程培训、实训基地和实习实训等方式培养学员的技能水平&#xff0c;并通过技能认证来评估其能力&#xff0c;以满足智慧安防行业对人才的需求&#xff0c;并…

el-date-picker 等 点击无反应不回显问题解决

如上图&#xff0c;编辑回显正常&#xff0c;但是时间控件在拖动过程中时间不会跟随改变。 解决办法&#xff1a; <el-date-picker input"onInput()" ...><el-input input"onInput()" ...>js中onInput() {this.$forceUpdate();},

Approaching (Almost) Any Machine Learning Problem中译版

前言 Abhishek Thakur&#xff0c;很多kaggler对他都非常熟悉&#xff0c;2017年&#xff0c;他在 Linkedin 发表了一篇名为Approaching (Almost) Any Machine Learning Problem的文章&#xff0c;介绍他建立的一个自动的机器学习框架&#xff0c;几乎可以解决任何机器学习问题…

企业的固定资产管理怎么操作

一家拥有多台大型设备的工厂&#xff0c;这些设备需要定期进行保养和维护&#xff0c;以确保其正常运转。而企业内部员工由于专业知识和技能的不同&#xff0c;需要分工协作才能更好地完成各项工作任务。因此&#xff0c;在设备资产管理方面&#xff0c;如何实现高效、便捷、透…

Linux x86_64 C语言实现gdb断点机制

文章目录 前言一、trap指令简介二、调用ptrace三、创建breakpoints四、CONT 和 SINGLESTEP五、完整代码演示六、增加参数检测参考资料 前言 本文参考文章&#xff1a;Implementing breakpoints on x86 Linux 一、trap指令简介 将通过在断点地址向目标进程的内存中插入一条新…

【数据库必备插件】Navicat Premium 15安装教程

软件下载 软件&#xff1a;Navicat Premium版本&#xff1a;15语言&#xff1a;简体中文大小&#xff1a;68.85M安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.0GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan…

【MATLAB第70期】基于MATLAB的LightGbm(LGBM)梯度增强决策树多输入单输出分类预测模型(全网首发,敬请期待)

【MATLAB第70期】基于MATLAB的LightGbm(LGBM)梯度增强决策树多输入单输出分类预测模型&#xff08;全网首发&#xff0c;敬请期待&#xff09; (LGBM)是一种基于梯度增强决策树(GBDT)算法。 基于MATLAB的LightGbm即将研究测试上线。 下一个研究对象&#xff1a; ABCBOOST模型 一…

【大数据之Kafka】六、Kafka Broker工作流程

1 Zookeeper存储的Kafka消息 &#xff08;1&#xff09;启动zookeeper可客户端 [lyxhadoop102 zookeeper-3.5.7]$ bin/zkCli.sh&#xff08;2&#xff09;通过ls命令查看Kafka相关信息 [zk: localhost:2181(CONNECTED) 0] ls /kafka2 Kafka Broker总体工作流程 Zookeeper集…

在springmvc框架中加入tomcat插件失败(找不到插件)

思考问题 在仓库里找不到tomcat插件&#xff0c;与springmvc无关&#xff0c;应该是tomcat版本错最终结论&#xff1a;tomcat插件没用过&#xff0c;所以在idea中找不到&#xff0c;需要从maven仓库中下载解法&#xff1a; 1、复制后面括号里的仓库地址&#xff0c;在浏览器打开…