Effective C++ 2e Item17

现代C++编程指南:C++17与C++20核心特性解析与实践 C++作为一门高性能系统编程语言,其核心价值在于提供对硬件资源的精细控制与零成本抽象。随着现代软件开发对代码安全性、可维护性和开发效率的要求不断提升,C++标准委员会通过引入新特性来优化语言设计,降低常见错误风险。从语言原理层面看,现代C++通过RAII(资源获取即初始化)等机制实现自动资源管理,利用模板元编程和编译期计算提升运行时性能。这些技术演进的实际价值体现在能够构建更健壮、更易维护的大型软件系统,特别是在高性能计算、游戏引擎、基础设施软件等场景中。本文聚焦于C++17和C++20两个重要版本,深入解 阅读详情

 

条款17: 在operator=中检查给自己赋值的情况

做类似下面的事时,就会发生自己给自己赋值的情况:

class X { ... };

X a;

a = a;                     // a赋值给自己

这种事做起来好象很无聊,但它完全是合法的,所以看到程序员这样做不要感到丝

毫的怀疑。更重要的是,给自己赋值的情况还可以以下面这种看起来更隐蔽的形式

出现:

a = b;

如果b是a的另一个名字(例如,已被初始化为a的引用),那这也是对自己赋值,

虽然表面上看起来不象。这是别名的一个例子:同一个对象有两个以上的名字。在

本条款的最后将会看到,别名可以以大量任意形式的伪装出现,所以在写函数时一

定要时时考虑到它。

在赋值运算符中要特别注意可能出现别名的情况,其理由基于两点。其中之一是效

率。如果可以在赋值运算符函数体的首部检测到是给自己赋值,就可以立即返回,

从而可以节省大量的工作,否则必须去实现整个赋值操作。例如,条款16指出,一

个正确的派生类的赋值运算符必须调用它的每个基类的的赋值运算符,所以在派生

类中省略赋值运算符函数体的操作将会避免大量对其他函数的调用。

另一个更重要的原因是保证正确性。一个赋值运算符必须首先释放掉一个对象的资

源(去掉旧值),然后根据新值分配新的资源。在自己给自己赋值的情况下,释放

旧的资源将是灾难性的,因为在分配新的资源时会需要旧的资源。

看看下面String对象的赋值,赋值运算符没有对给自己赋值的情况进行检查:

class String {
public:
  String(const char *value);    // 函数定义参见条款11
                                //

  ~String();                    // 函数定义参见条款11
                                //
  ...

  String& operator=(const String& rhs);

private:
  char *data;
};

// 忽略了给自己赋值的情况
// 的赋值运算符
String& String::operator=(const String& rhs)
{
  delete [] data;    // delete old memory

  // 分配新内存,将rhs的值拷贝给它
  data = new char[strlen(rhs.data) + 1];
  strcpy(data, rhs.data);

  return *this;      // see Item 15
}

看看下面这种情况将会发生什么:

String a = "Hello";

a = a;               // same as a.operator=(a)

赋值运算符内部,*this和rhs好象是不同的对象,但在现在这种情况下它们却恰巧

是同一个对象的不同名字。可以这样来表示这种情况:

*this  data ------------> "Hello/0"
                  /
                 /
rhs    data -----

赋值运算符做的第一件事是用delete删除data,其结果将如下所示:

*this  data ------------> ???
                   /
                  /
rhs    data -----

现在,当赋值运算符对rhs.data调用strlen时,结果将无法确定。这是因为data被

删除的时候rhs.data也被删除了,data,this->data 和rhs.data 其实都是同一个

指针!从这一点看,情况只会越变越糟糕。

现在可以知道,解决问题的方案是对可能发生的自己给自己赋值的情况先进行检查

,如果有这种情况就立即返回。不幸的是,这种检查说起来容易做起来难,因为你

必须定义两个对象怎么样才算是“相同”的。

你面临的这个问题学术上称为object identity,它在面向对象领域是个很有名的

论题。本书不是讲述object identity的地方,但有必要提到两个解决这个问题的

基本方法。

一个方法是,如果两个对象具有相同的值,就说它们是相同的(具有相同的身份)

。例如,两个String对象如果都表示的是相同顺序的字符序列,它们就是相同的:

String a = "Hello";
String b = "World";
String c = "Hello";

a和c具有相同值,所以它们被认为是完全相同的;b和它们都不同。如果把这个定

义用到String类中,赋值运算符看起来就象这样:

String& String::operator=(const String& rhs)
{
  if (strcmp(data, rhs.data) == 0) return *this;

  ...

}

值相等通常由operator==来检测,所以对于一个用值相等来检测对象身份的类C来

说,它的赋值运算符的一般形式是:

C& C::operator=(const C& rhs)
{
  // 检查对自己赋值的情况
  if (*this == rhs)            // 假设operator=存在
    return *this;

  ...

}

注意这个函数比较的是对象(通过operator=),而不是指针。用值相等来确定对

象身份和两个对象是否占用相同的内存没有关系;有关系的只是它们所表示的值。

另一个确定对象身份是否相同的方法是用内存地址。采用这个定义,两个对象当且

仅当它们具有相同的地址时才是相同的。这个定义在C++程序中运用更广泛,可能

是因为它很容易实现而且计算很快,而采用值相等的定义则不一定总具有这两个优

点。采用地址相等的定义,一个普通的赋值运算符看起来象这样:

C& C::operator=(const C& rhs)
{
  // 检查对自己赋值的情况
  if (this == &rhs) return *this;

  ...

}

它对很多程序都适用。

如果需要一个更复杂的机制来确定两个对象是否相同,这就要靠程序员自己来实现

。最普通的方法是实现一个返回某种对象标识符的成员函数:

class C {
public:
  ObjectID identity() const;    // 参见条款36

  ...

};

对于两个对象指针a和b,当且仅当 a->identity() == b->identity()的时候,它

们所指的对象是完全相同的。当然,必须自己来实现ObjectIDs的operator==。

别名和object identity的问题不仅仅局限在operator=里。在任何一个用到的函数

里都可能会遇到。在用到引用和指针的场合,任何两个兼容类型的对象名称都可能

指的是同一个对象。下面列出的是别名出现的其它情形:

class Base {
  void mf1(Base& rb);          // rb和*this可能相同                     
  ...

};

void f1(Base& rb1,Base& rb2);  // rb1和rb2可能相同
                               //

class Derived: public Base {
  void mf2(Base& rb);          // rb和*this可能相同
                               //
  ...

};

int f2(Derived& rd, Base& rb); // rd和rb可能相同
                               //

这些例子刚好都用的是引用,指针也一样。

可以看到,别名可以以各种形式出现,所以决不要忘记它或期望自己永远不会碰到

它。也许你不会碰到,但我们大多数会碰到。而很明显的一条是,处理它会达到事

半功倍的效果。所以任何时候写一个函数,只要别名有可能出现,就必须在写代码

时进行处理。

积木报表导出数据量太大报错 ‌性能问题‌:‌当数据量非常大时,‌积木报表在导出过程中可能会遇到性能瓶颈,‌导致导出过程缓慢甚至失败。‌解决这一问题的方法包括优化SQL查询、‌调整分页大小、‌使用更适合处理大量数据的库(‌如阿里巴巴的EasyExcel)‌进行导出,‌或者采用异步处理的方式来避免接口超时1。‌内存溢出‌:‌如果报表工具或其依赖的库不支持流式(‌分批)‌导出,‌大量数据的处理可能会导致内存溢出。‌数据处理方式‌:‌如果待导出的数据包含特殊字符,‌如英文双引号或等号,‌这可能导致JSON反序列化报错。 阅读详情

相关推荐

FPGA实现16x16点阵汉字滚动显示:Verilog代码与Quartus仿真详解

本文详细介绍了如何使用FPGA实现16x16点阵汉字滚动显示,包括Verilog代码设计、Quartus仿真及硬件连接技巧。通过字模数据存储和扫描控制逻辑,实现汉字的动态显示效果,并提供了优化建议和常见问题解决方案,适合FPGA开发者和硬件爱好者学习参考。

weixin_29172963的博客 354

【c++编程思想学习笔记】operator=为何要检查自赋值的解答

做类似下面的事时,就会发生自己给自己赋值的情况: class X { ... }; X a; a = a;                     // a赋值给自己 复制代码 这种事做起来好象很无聊,但它完全是合法的,所以看到程序员这样做不要感到丝毫的怀疑。更重要的是,给自己赋值的情况还可以以下面这种看起来更隐蔽的形式出现: a = b; 如果b是a的另一个名字

zyh_helen的专栏 1716

2022-2028年全球与中国玄武岩连续纤维(BCF)行业深度分析

本文研究全球与中国市场玄武岩连续纤维(BCF)的发展现状及未来发展趋势,分别从生产和消费的角度分析玄武岩连续纤维(BCF)的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。 主要生产商包括: Kamenny Vek TESPE Mafic Technobasalt Russian Basalt ISOMATEX SA Sudaglass Basalt Fiber INNEGRA LAVAint

Fj69258的博客 617

关于对象的自我赋值行为

所谓的自我赋值,指得就是一个对象赋值给自己的简单行为,但这种看起来人畜无害动作,在某些情形下却可能会使得你的代码崩溃。自我赋值的语句,就像这样:Widget w;w = w;很明显,这是一段愚蠢的代码。但既然我们提到自我赋值会引发问题,那我们先来澄清一下自我赋值的情况其实有时并不是那么显而易见的,并不一定都像上述代码那么愚蠢,它们还可能是这样:a[i] = a[j];*px...

weixin_34112030的博客 993

Plot3d 网格数据格式

PLOT3D是一个计算机图形程序,旨在可视化计算流体力学的网格和解决方案。PLOT3D文件可以是ASCII, 也可是Fortran unformatted 或 C binary形式。PLOT3D文件分为网格文件(XYZ 文件), 空气动力学结果文件 (Q 文件)和通用结果文件(函数文件 + 函数名称文件)。网格文件中可加入所谓的IBlank参数。

weixin_62592231的博客 3722

基于本地知识库的智能问答系统架构

智能问答系统的核心可以分为4部分。1)知识库构建。2)检索。3)后处理。4)推理。知识库构建:知识库中可以包含多个向量数据库和知识图谱等,核心是怎么建立索引,使得检索时能够将与问题相关的知识给检索出来。检索:从知识库中的多个库或者知识图谱中检索出与问题相关的信息。后处理:从知识库中检索出来的信息可能是冗余的,而且可能不符合我们希望的格式要求,因此,我们需要对这些数据进行过滤,格式化等操作。推理: 基于检索出来的知识进行推理,让输出能够回答用户的问题,目前主要使用大模型进行推理。

qq_44993593的博客 1711

C++ 重载运算符“=”检测自赋值

重载赋值运算符 赋值运算符可能是最容易令人迷惑的一个,所以,重载它必须十分的小心。 1. 值运算符仅能重载为成员函数。  C++不允许赋值运算符被重载为全局形式,这是因为如果可以写出全局形式的赋值运算符函数的话,我们可以写出这样的函数:  iint operator=(int a, integer b); 从而出现这样无法无天的语句: integer a(3);

wo17fang的专栏 3083

NFT图片批量合成及稀有度计算工具

一、制作背景 -当前NFT作品制作的大多方式都是使用部件拼接的形式,然后从大量的可能性结果取优; 二、解决问题 拼接合成的组合数量十分巨大,手工合成费时费力; 合成中对各个部件的使用次数不容易控制,部位出现的概率对作品的稀有度及评分影响较大; 对于生成的作品各个部位的信息(包括来源和使用率)统计繁琐,不利于对作品的介绍中体现; 三、程序功能介绍 1、功能自定义部位稀有度批量合成NFT作品 步骤一:创建各个合成部件文件夹,将图片放到文件夹内 步骤二:软件上设置各个部件对于的图片文件夹及出现的概率 步骤二

Keeping111的博客 4638

Effective_C++:17、在operator=中检查是否“自己赋值给自己”

17、在operator=中检查是否“自己赋值给自己” 1、为何检查是否“自己赋值给自己” 在使用赋值操作符时,我们只要求调用的对象类型、参数类型正确即可。所以,自己赋值给自己是完全合法的,如可能将一个对象的别名作为另一个对象赋值给这个对象。发生自己赋值给自己,可能会发生一些问题,为此,我们需要检查是否“自己赋值给自己”。 1.为了确保正确性。使用赋值操作符给左边的对象配置新资源时,可能需要...

l1dian11的博客 645

Google Earth Engine(GEE)——全球影像数据正确下载方式和注意事项

今天看到有人担心全球影像能不能下载的问题? 有时候我们会用到全球的数据,但是如何下载全球的数据呢? 首先如果没有全球的矢量边界我们会不会无法下载全球的数据,答案当然是可以的,因为我们在边界筛选的过程中filterBounds(),就是为了筛选边界,当我们不进行边界筛选的时候,自然而然默认的边界范围就是全球的,但是紧接着会面临其他两个问题:1如何下载?2如何在不缩小分辨率的情况下下载成功? 对于第一个问题我们如何下载?还是按照正常的逻辑利用export直接倒出到Google硬盘当中, 代码:

此星光明博客 3772

【人工智能】Open WebUI 安装指南 ( 基于Docker下载和使用Open WebUI 、通过本地运行项目源码进行搭建Open WebUI)

Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 WebUI,旨在完全离线操作。它支持各种 LLM 运行程序,包括 Ollama 和 OpenAI 兼容的 API。社区:https://openwebui.com/

奶爸的编程之路,也就一周冷个三天 2万+

《Effective C++》学习笔记(条款11:在operator=中处理“自我赋值”)

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!! “自我赋值”的意思是自己给自己赋值,如: class Widget{...}; Widget w; w = w; //自己给自己赋值 这看起来很蠢,但它合法,所以不要认为它不会发生,此外,赋值动作并不会被你一眼看出来(如w = w;),例如: Widget arr[10] = {...}; arr[i] = arr[j]; //潜在的自我赋值,arr[i]和arr[j]存储同一个

ZLX的博客 714

【技术科普】破解政务跨系统数据壁垒!亘川一网统管平台多源数据同步技术解析

本文解析亘川一网统管平台多源数据同步技术,针对政务跨系统数据壁垒难题,介绍组件的数据采集、校验、审计留痕等核心能力,结合营商环境项目落地案例,对比传统开发模式差异,讲解开源组件的实践价值,为政务项目开发者提供落地参考。

weixin_67440240的博客 635

Effective C++ 2e Item47

条款47: 确保非局部静态对象在使用前被初始化大家都是成年人了,所以用不着我来告诉你们:使用未被初始化的对象无异于蛮干。事实上,关于这个问题的整个想法会让你觉得可笑;构造函数可以确保对象在创建时被初始化,难道不是这样吗?唔,是,也不是。在某个特定的被编译单元(即,源文件)中,可能一切都不成问题;但如果在某个被编译单元中,一个对象的初始化要依赖于另一个被编译单元中的另一个对象的值,并且这第二

lostmouse的专栏 1339

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Effective C++ 2e Item36

条款36: 区分接口继承和实现继承(公有)继承的概念看起来很简单,进一步分析,会发现它由两个可分的部分组成:函数接口的继承和函数实现的继承。这两种继承类型的区别和本书简介中所讨论的函数声明和函数定义间的区别是完全一致的。作为类的设计者,有时希望派生类只继承成员函数的接口(声明);有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现;有时则希望同时继承接口和实现,并且不允许派生类改写

lostmouse的专栏 1008

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Effective C++ 2e Item16

条款16: 在operator=中对所有数据成员赋值条款45说明了如果没写赋值运算符的话,编译器就会为你生成一个,条款11则说明了为什么你会经常不喜欢编译器为你生成的这个赋值运算符,所以你会想能否有个两全其美的办法,让编译器生成一个缺省的赋值运算符,然后可以有选择地重写不喜欢的部分。这是不可能的!只要想对赋值过程的某一个部分进行控制,就必须负责做赋值过程中所有的事。实际编程中,这意味着写赋

lostmouse的专栏 979

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Modern Effective C++ item 15:尽可能的使用constexpr

当constexpr应用于函数时,表示函数可以在编译期执行,因为参数也是编译期常量。常量表达式构造函数拥有和常量表达式函数相同的退化特性,当它的实参不是常量表达式的时候,构造函数可以退化为普通构造函数,当然,这么做的前提是类型的声明对象不能为常量表达式值。正常工作,因为 10 是一个常量表达式,size(10) 可以在编译时计算其结果,并用于初始化 constexpr 变量 s_constexpr。constexpr还能定义一个常量表达式函数,即constexpr函数,常量表达式函数的返回值可以在编译。

_ 1120

Effective C++ 2e Item20

条款20: 避免public接口出现数据成员首先,从“一致性”的角度来看这个问题。如果public接口里都是函数,用户每次访问类的成员时就用不着抓脑袋去想:是该用括号还是不该用括号呢?——用括号就是了!因为每个成员都是函数。一生中,这可以避免你多少次抓脑袋啊!你不买“一致性”的帐?那你总得承认采用函数可以更精确地控制数据成员的访问权这一事实吧?如果使数据成员为public,每个人都可以对它

lostmouse的专栏 989

[翻译] Effective C++, 3rd Edition, 附录 B. 第二和第三版之间的 Item 映射

附录 B. 第二和第三版之间的 Item 映射作者:Scott Meyers译者:fatalerror99 (iTePubs Nirvana)发布:http://blog.youkuaiyun.com/fatalerror99/Effective C++ 的第三版在很多方面与第二版不同,其中最引人注目的是它包含很多新的信息。然而,第二版的大部分内容依然保留在第三版中,虽然经常会改变形式和位置

艺术编程 5819

Effective C++ Item 17 以独立语句将newed对象置入智能指针

经验:以独立语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。 示例:可能造成资源泄漏 processWidget(std::tr1::shared_ptr<Widget>(newWidget), priority()); 解析: 上述代码要做以下三件事 1. 执行“new Widget” 2. 调用priority 3. 调用tr1::shared_ptr构造函数 这三件事的执行次序不确定,编译器是按上面123的次

889

读书笔记 effective c++ Item 52 如果你实现了placement new,你也要实现placement delete

1. 调用普通版本的operator new抛出异常会发生什么? Placement new和placement delete不是C++动物园中最常遇到的猛兽,所以你不用担心你对它们不熟悉。当你像下面这样实现一个new表达式的时候,回忆一下Item 16和Item 17: 1 Widget *pw = new Widget;   两个函数会被调用:一个是调用operator new来分配...

HarlanC的专栏 207

读书笔记 effective c++ Item 48 了解模板元编程

1. TMP是什么? 模板元编程(template metaprogramming TMP)是实现基于模板的C++程序的过程,它能够在编译期执行。你可以想一想:一个模板元程序是用C++实现的并且可以在C++编译器内部运行的一个程序,它的输出——从模板中实例化出来的C++源码片段——会像往常一样被编译。 2. 使用TMP的优势 如果这没有冲击到你,是因为你没有足够尽力去想。   C++不是...

HarlanC的专栏 200

读书笔记 effective c++ Item 5 了解c++默认生成并调用的函数

1 编译器会默认生成哪些函数  什么时候空类不再是一个空类?答案是用c++处理的空类。如果你自己不声明,编译器会为你声明它们自己版本的拷贝构造函数,拷贝赋值运算符和析构函数,如果你一个构造函数都没有声明,编译器同样会为你声明一个默认拷贝构造函数。这些所有的函数会是public和inline的(Item30)。因此,如果你写了下面的类: 1 class Empty{}; 本质上来说和下面的类...

HarlanC的专栏 179

读书笔记 effective c++ Item 13 用对象来管理资源

正文回到顶部1.不要手动释放从函数返回的堆资源假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来,1classInvestment{...};//rootclassofhierarchyof23//investmenttypes进一步假设这个程序库通过一个工厂函数(Item 7)来...

weixin_34007886的博客 64

Effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)

假设你正在操作一个Rectangle类。每个矩形可以通过左上角的点和右下角的点来表示。为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放入一个辅助结构体中,Rectangle中声明一个指向它的指针就可以了: 1 class Point { // class for repre

小小小程序员的博客 218
上一篇: Effective C++ 2e Item16
下一篇: Effective C++ 2e Item18
lostmouse
博客等级 码龄26年 22粉丝 63原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值