Modern Effective C++:Item 6 auto推导若非己愿,使用显式类型初始化惯用法

devtools/2024/11/24 20:42:13/
前置:vector<bool>

首先vector<bool>并不是一个通常意义上的vector容器。早在C++98的时候,就有vector< bool>,但是因为当时为了考虑到节省空间,所以vector<bool>里面不是一个Byte储存的,它是一个bit一个bit储存的。

因为没有直接去给一个bit来操作,所以用operator[]的时候,正常容器返回的应该是一个对应元素的引用,但是对于vector< bool>实际上访问的是一个"proxy reference"而不是一个"true reference",返回的是std::vector< bool>:reference类型的对象。

一般情况:

vector c {false, true, false, true, false};
bool b=c[0];
auto d=c[0];

b的初始化暗含一个隐式的类型转换。对于d,类型并不是bool,而是一个vector<bool>中的一个内部类。此时如果修改d的值,c中的值也会跟着修改。如果c被销毁,d就会变成一个悬垂指针,再对d操作就属于未定义行为。所以对于容器一些基本的操作它并不能满足,诸如取地址给指针初始化操作,没有办法给单一bit来取地址或者引用。

vector c{ false, true, false, true, false };
bool &tmp = c[0]; //错误,不能编译,对于引用来说,因为c[0]不是一个左值。
bool *p = &c[0]; //错误,不能编译,因为无法将一个临时量地址给绑定到指针。

std::vector<bool>的operator[] 实际上返回的是一个 std::vector<bool>::reference 类型的对象,这是一个代理类,它模仿了 bool& 的行为。这个代理类提供了对单个位的操作能力,包括读取和设置位的值。std::vector<bool>::reference 是 std::vector<bool> 特化中使用的一个代理类。由于 std::vector<bool> 以位的形式存储布尔值,导致了与普通 std::vector<T> 行为上的差异。std::vector<bool> 不能像其他 std::vector<T> 那样直接返回 bool& 类型的引用,因为 C++ 语言不允许对单独的位进行引用。为了解决这个问题,std::vector<bool> 使用了一个代理类 std::vector<bool>::reference 来模拟 bool& 的行为。std::vector<bool>::reference 包含一个指向内部位数组的指针:这个指针指向存储布尔值的整数。

一个表示位位置的索引:这个索引表示具体的位在整数中的位置。

#include <iostream>
#include <vector>
int main() {std::vector<bool> v = {true, false, true, false};// 获取第 2 个元素的引用std::vector<bool>::reference ref = v[2];// 打印第 2 个元素的值std::cout << "v[2] = " << ref << std::endl;  // 输出: v[2] = 1// 修改第 2 个元素的值ref = false;// 再次打印第 2 个元素的值std::cout << "v[2] = " << v[2] << std::endl;  // 输出: v[2] = 0return 0;
}

前置:代理类

待整理

使用 auto 时的问题

当使用 auto 来声明变量并初始化时,编译器会根据初始化表达式的类型来推断变量的类型。对于 std::vector<bool> 的 operator[],auto 会推断出 std::vector<bool>::reference 而不是 bool。这可能导致问题,特别是当 std::vector<bool> 是一个临时对象时,std::vector<bool>::reference 中的指针会变成悬空指针,导致未定义行为。

调用features将返回一个std::vector<bool>临时对象,这个对象没有名字,为了方便我们的讨论,我这里叫他temp。operator[]在temp上调用,它返回的std::vector<bool>::reference包含一个指向存着这些bits的一个数据结构中的一个word的指针(temp管理这些bits),还有相应于第5个bit的偏移。highPriority是这个std::vector<bool>::reference的拷贝,所以highPriority也包含一个指针,指向temp中的这个word,加上相应于第5个bit的偏移。在这个语句结束的时候temp将会被销毁,因为它是一个临时变量。因此highPriority包含一个悬置的指针,如果用于processWidget调用中将会造成未定义行为。

processWidget(w, highPriority); //未定义行为.highPriority包含一个悬置指针.

下面这个没看懂。

// 模拟 std::vector<bool>::reference 类
class vector_bool_reference {friend class vector_bool;//私有成员unsigned char* ptr;//指向存储位的整数的指针unsigned char index;//位的位置//私有构造函数vector_bool_reference(unsigned char* p, unsigned char i) noexcept:ptr(p),index(i) {}
public://析构函数~vector_bool_reference() = default;//转换为 booloperator bool() const noexcept {return (*ptr & (1 << index)) != 0;}//从 bool 赋值vector_bool_reference& operator=(const bool x) noexcept {if (x) {*ptr |= (1 << index);} else {*ptr &= ~(1 << index);}return *this;}//从另一个 reference 赋值vector_bool_reference& operator=(const vector_bool_reference& x) noexcept {*ptr = ((*ptr & ~(1 << index)) | (static_cast<bool>(x) << index));return *this;}// 翻转位的值void flip() noexcept {*ptr ^= (1 << index);}
};
class vector_bool {unsigned char* data;  // 指向存储位的数组size_t size_;         // 位的数量
public:vector_bool(size_t n) : data(new unsigned char[(n + 7) / 8]), size_(n) {for (size_t i = 0; i < (n + 7) / 8; ++i) {data[i] = 0;}}~vector_bool() {delete[] data;}//获取位的引用vector_bool_reference operator[](size_t pos) {return vector_bool_reference(&data[pos / 8], pos % 8);}

为了避免这些问题,建议显式地将结果转换为 bool:

bool highPriority = features(w)[5];

或者使用 static_cast 显式转换:

bool highPriority = static_cast<bool>(features(w)[5]);

这样可以确保 highPriority 是一个真正的 bool 变量,而不是一个代理类实例。

一些代理类被设计于用以对客户可见。比如std::shared_ptr和std::unique_ptr。其他的代理类则或多或少不可见,比如std::vector<bool>::reference就是不可见代理类的一个例子,还有它在std::bitset的胞弟std::bitset::reference。

在不可见代理类里一些C++库也是用了表达式模板(expression templates)。

这些库通常被用于提高数值运算的效率。给出一个矩阵类Matrix和矩阵对象m1,m2,m3,m4,举个例子,这个表达式

Matrix sum = m1 + m2 + m3 + m4;

可以使计算更加高效,只需要使让operator+返回一个代理类代理结果而不是返回结果本身。也就是说,对两个Matrix对象使用operator+将会返回如Sum<Matrix, Matrix>这样的代理类作为结果而不是直接返回一个Matrix对象。在std::vector<bool>::reference和bool中存在一个隐式转换,同样对于Matrix来说也可以存在一个隐式转换允许Matrix的代理类转换为Matrix,这让表达式等号“=”右边能产生代理对象来初始化sum。(这个对象应当编码整个初始化表达式,即类似于Sum<Sum<Sum<Matrix, Matrix>, Matrix>, Matrix>的东西。客户应该避免看到这个实际的类型)作为一个通则,不可见的代理类通常不适用于auto。这样类型的对象的生命期通常不会设计为能活过一条语句,所以创建那样的对象你基本上就走向了违反程序库设计基本假设的道路。std::vector<bool>::reference就是这种情况,违反这个基本假设将导致未定义行为。

因此想避开这种形式的代码:

auto someVar = expression of "invisible" proxy class type;

显式类型初始器惯用法使用auto声明一个变量,然后对表达式强制类型转换(cast)得出你期望的推导结果。例:

auto highPriority = static_cast<bool>(features(w)[5]);

features(w)[5]还是返回一个std::vector<bool>::reference对象,就像之前那样,但是这个转型使得表达式类型为bool,然后auto才被用于推导highPriority。在运行时,std::vector<bool>::operator[]返回的std::vector<bool>::reference执行它支持的向bool的转型,在这个过程中指向std::vector<bool>的指针已经被解引用。这避开了我们之前的未定义行为。对于Matrix来说,显式类型初始器惯用法如下:

auto sum = static_cast<Matrix>(m1 + m2 + m3 + m4);


http://www.ppmy.cn/devtools/136638.html

相关文章

一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?- 多语言

目录 C 语言实现 Python 实现 Java 实现 Js 实现 题目&#xff1a;一个整数&#xff0c;它加上100后是一个完全平方数&#xff0c;再加上168又是一个完全平方数&#xff0c;请问该数是多少&#xff1f; 程序分析&#xff1a; 假设该数为 x。 1、则&#xff1a;x 100 …

利用c语言详细介绍下插入排序

插入排序&#xff0c;被称为直接插入排序。它的基本思想是将一个记录插入到已经排好序的有序表中&#xff0c;从而一个新的、记录数增 1 的有序表。 一、图文介绍 我们还是使用数组【10&#xff0c;5&#xff0c;3&#xff0c;20&#xff0c;1]&#xff0c;排序使用升序的方式&…

【大数据学习 | Spark-Core】yarn-client与yarn-cluster的区别

1. yarn的提交命令 # yarn的提交命令参数 --master yarn #执行集群 --deploy-mode # 部署模式 --class #指定运行的类 --executor-memory #指定executor的内存 --executor-cores # 指定核数 --num-executors # 直接指定executor的数量 --queue # 指定队列 2. yarn-client模式…

【qt】控件QLabel

1.Qlabel QLabel的文本显示 Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->label->setTextFormat(Qt::PlainText);//设置文本格式为纯文本ui->label->setText("这是纯文本");//设置文本内容ui->…

40分钟学 Go 语言高并发:Select多路复用

Select多路复用 学习目标 知识点掌握程度应用场景select实现原理深入理解底层机制channel通信和多路选择超时处理掌握超时控制方法避免阻塞和资源浪费优先级控制理解优先级实现处理多个channel的顺序性能考虑了解性能优化点高并发场景优化 1. Select实现原理 让我们通过一个…

光伏电站项目-视频监控、微气象及安全警卫系统

一、项目背景 近年来&#xff0c;我国光伏发电持续快速发展。截止2019年5月装机总容量超过2.043亿千瓦&#xff0c;技术水平不断提升&#xff0c;成本显著降低&#xff0c;开发建设质量和消纳利用明显改善&#xff0c;在部分地区实现了家庭分布式光伏并入电网&#xff0c;为建…

MATLAB 2024a安装包下载及安装教程

[安装环境]: Win 11/Win 10 MATLAB和Mathematica、Maple并称为三大数学软件。它在数学类科技应用软件中在数值计算方面首屈一指。行矩阵运算、绘制函数和数据、实现算法、创建用户界面、连接其他编程语言的程序等。MATLAB的基本数据单位是矩阵&#xff0c;它的指令表达式与数学…

【入门篇】哥德巴赫猜想——多语言求解版

# [NOIP2004 提高组] 津津的储蓄计划 题目描述 津津的零花钱一直都是自己管理。每个月的月初妈妈给津津 300 300 300 元钱&#xff0c;津津会预算这个月的花销&#xff0c;并且总能做到实际花销和预算的相同。 为了让津津学习如何储蓄&#xff0c;妈妈提出&#xff0c;津津…