操作符

条款5:对定制的 “类型转换函数” 保持警觉

c++允许编译器在不同类型之间执行隐式转换。但是部分隐式转换存在安全性问题,例如 int–>short,
double–>char。然而,自己设计类型时可以尝试提供特定的 “函数” 来作为隐式类型转换使用,并保证安
全性。

[!tip]
尽管如此,最好不要提供任何类型转换函数。

实现定制的类型转换函数有两种方案。

1. 单自变量 constructors

单自变量 constructors 指能够以单一自变量成功调用的 constructors,也可以是拥有多个参数,但除
了第一个参数之外都有默认值。

class Name {public:Name(const string &s); // 可以将 string 转换为 Name
};class Rational {public:Rational(int numerator = 0, int denominator = 1); // 可以将 int 转换为 Rational
};

2. 隐式类型转换操作符

隐式类型转换操作符,是一个拥有奇怪名称的 member function:在关键词 operator 之后加上一个类
型名称,并且不制定返回值类型。

class Rational {public:operator double() const; // 将 Rational 转换为 double
}Rational r(1, 2);
double d = 0.5 * r; // r 自动 转换为 double

3. 最好不要提供任何类型转换函数

因为在未预期的情况下,此类函数可能会被调用,而结果可能不正确,不直观且难以调试。

a. 隐式类型转换操作符

针对隐式类型转换操作符,可能的情况如下:

Rational r(1, 3);
std::cout << r; // 期待打印 "1/3"

如果 Rational 中并为提供 operator<< 方法,但却提供了隐式类型转换操作符,这时就会发生意想不到
的结果。编译器会将 Rational 转换为 double 进行打印,这显然并不是期待的结果。

为了解决这个问题,应当使用一个函数来取代隐式类型转换操作符,例如 asDouble。

class Rational {public:double asDouble() const;
}Rational r(1, 3);
std::cout << r; // 期待打印 "1/3"
std::cout << r.asDouble() // 期待打印 0.333333

b. 单自变量 constructors

通过单自变量的 constructor 完成的隐式转换是很难消除的。

template<class T>
class Array {public:Array(int lowBound, int highBound);Array(int size);T& operator[] (int index);
};bool operator==(const Array<int>& lhs, const Array<int>& rhs);Array<int> a(10);
Array<int> b(10);
for (int i = 0; i < 10; ++i) {if (a == b[i]) { } // 这里写错了,所以会造成额外的问题。else { }
}

上述代码的一个由于写错,所以产生了一个意想不到的问题。我们撰写这段代码的本意是,如果 a[i] == b[i]
成立,就做一些事。然而由于写成了 a,于是编译器为我们选择了我们上面写的这段 operator==。首先,
a 可以匹配 const Array& lhs,那 b[i] 如何匹配后者呢?通过隐式类型转换,编译器通过 Array(int size)
的构造方法,将 b[i] 传入构造出一个临时对象,这样就匹配成功了。

为了拒绝这种事情的发生,使用 explicit 关键词,来要求该构造函数必须显式使用。

template<class T>
class Array {public:explicit Array(int size);
};
if (a == b[i]) // 非法行为
if (a == Array<int>(b[i])) // 合法行为,但是逻辑错误
if (a == static_cast<Array<int>>(b[i])) // 合法,同样逻辑错误
if (a == (Array<int>)b[i]) // 合法,但是逻辑错误

如果你的编译器还不支持 explicit 关键字,那么你可以使用 proxy class 这种方案进行:

template<class T>
class Array {public:class ArraySize {public:ArraySize(int num) : theSize(num) {}int size() const { return theSize; }private:int theSize;}explicit Array(ArraySize size);
};if (a == b[i]) // 非法

这种类型转换是不能成功的,因为 num 需要首先隐式转换为 Array::ArraySize,然后再由 ArraySize
转换为 Array。这种连续多次转换的行为是被编译器禁止的。

条款6:区别 increment/decrement 操作符的 前置 和 后置 形式

为了区分 ++/-- 的前置形式和后置形式,重载函数以参数类型来区分彼此。

class UPInt { // unlimited precision int
public:UPInt(int value) : _value(value) {}UPInt &operator++() { // 前置形式(*this)._value += 1;return *this;}const UPInt operator++(int) { // 后置形式UPInt oldValue = *this;++(*this);return oldValue;}UPInt &operator--() {(*this)._value -= 1;return *this;}const UPInt operator--(int) {UPInt oldValue = *this;--(*this);return oldValue;}friend ostream &operator<<(ostream &os, UPInt);
private:int _value;
};

后置式操作符没有使用传入的参数,只是为了区别前置式和后置式而已。为了避免发出警告,可以故意省略参
数名称。前置式返回一个 reference,而后置式返回一个 const 对象。这种行为主要是为了和内建类型保
持一致,以避免错误。

UPInt upi;
++++upi; // 合理
upi++++; // 不合理

除此之外,关于效率问题,显然 后置式 通过调用 前置式 来完成任务,同时还生成一个临时对象进行存储。
因此,前置式效率更高。

[!tip]
选择的原则是:除非真的需要后置式的行为(返回 const 对象),否则使用前置式。

条款7:千万不要重载 && || 和 , 操作符

在进行 “真假值” 判断时,c/c++ 采用“骤死式”的判断方案,即从左到右进行判断,如果左面不成立则不继
续判断。这使得这三个操作符具有顺序性,而对其进行重载会导致破环这种秩序性,从而导致意想不到的结果。

因此应该选择不去重载这些操作符。

条款8:了解各种不同意义的 new 和 delete

1. new

主要需要理解 new operator 和 operator new 之间的差异。

new operator 是类似于 string *ps = new string("hello world"); 中的new。这个操作符是
由语言内建的,不能被改变。它一般执行两个操作:1. 分配一个足够防止类型对象的内存空间。2. 调用一个
constructor,为刚才分配的内存中那个对象设定初值。

operator new 则是用来申请内存空间的函数,通常这样使用void *rawMemory = operator new(sizeof(string))
此外,operator new还可以被重载,改变其行为。

需要注意的是,只要使用 new operator,必定会默认重新使用 operator new 来分配一块空间,并调用
constructor。通常情况下无法直接在一块已分配的空间上直接调用 constructor。

placement new

如果需要在一些分配好的原始内存中,直接调用 constructor 来构建对象。有一个特殊版本的 operator
new 被成为 placement new,允许你这么做。

#include <new> // 使用 placement new 需要用 <new>class Widget {public:Widget(int widgetSize) : _size(widgetSize) {}int size() {return _size;}
private:int _size;
};int main() {void *rawMemory = operator new(sizeof(Widget));Widget *pw = static_cast<Widget *>(rawMemory);new (pw) Widget(12); // 作为placement new 所需的第二个参数制定内存位置
}

2. deletion and Deallocation

与 new operator 和 operator new 的关系一样,delete operator 和 operator delete 也是
类似却不同的两个操作。

delete operator 与 new operator 相对应,它也执行两步操作:1. 调用对象的 destructor 方法,
从而释放对象中其他引用的内存空间。2. 执行 operator delete 将申请的空间还给内存。

而 operator delete 与 operator new 相对应,只进行空间的释放。这也就对使用者提出了新的要求,
必须手动调用对象的 destructor 方法才行。

int main() {void *rawMemory = operator new(sizeof(Widget));Widget *pw = static_cast<Widget *>(rawMemory);new (pw) Widget(12);pw->~Widget();operator delete(rawMemory);
}

需要注意的是,不能直接使用 delete operator 来释放 pw 指针指向的内容,因为该过程并非使用 new
operator 来实现的,而是使用 placement new 来完成的,会产生错误。

3. 数组

operator new/delete 也可以处理数组内容,对应的使用方法是

int main() {void *rawMemory = operator new[](sizeof(Widget) * 10);operator delete[](rawMemory);
}

More Effective C++——2. 操作符相关推荐

  1. More Effective C++之 Item M6:自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别

    很久以前(八十年代),没有办法区分++和--操作符的前缀与后缀调用.这个问题遭到程序员的报怨,于是C++语言得到了扩展,允许重载increment 和 decrement操作符的两种形式. 然而有一个 ...

  2. Effective C++ 50条款

    Effective C++ 50条款 条款 1:尽量用 const 和 inline 而不用#define--尽量用编译器而不用预处理 #define max(a,b) ((a) > (b) ? ...

  3. Effective STL 50条有效使用STL的经验笔记

    Scott Meyers大师Effective三部曲:Effective C++.More Effective C++.Effective STL,这三本书出版已很多年,后来又出版了Effective ...

  4. 第 3 次读 Effective Java,这 58 个技巧最值!

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 来源:Dong GuoChao <Effective ...

  5. Effective C++ 之 Item 6 : 若不想使用编译器自动生成的函数,就该明确拒绝

    Effective C++ chapter 2. 构造 / 析构 / 赋值运算 (Constructors, Destructors, and Assignment Operators) Item 6 ...

  6. More Effective C++:理解new和delete

    转载自: http://dev.yesky.com/242/2585242.shtml 人们有时好像喜欢故意使C++语言的术语难以理解.比如说new操作符(new operator)和operator ...

  7. 读完《Effective Java》后,总结了 50 条开发技巧

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | Dong GuoChao 来源 | https ...

  8. 声明及赋值_重述《Effective C++》二——构造、析构、赋值运算

    关于本专栏,请看为什么写这个专栏.如果你想阅读带有条款目录的文章,欢迎访问我的主页. 构造和析构一方面是对象的诞生和终结:另一方面,它们也意味着资源的开辟和归还.这些操作犯错误会导致深远的后果--你需 ...

  9. Effective C++ --7 模板与泛型编程

    上一篇Effective C++ -- 6 继承与面向对象设计 41.了解隐式接口和编译器多态 (1)class和template都支持接口和多态.Class支持显示接口,多态是指virtual引起的 ...

最新文章

  1. Sea.js学习4——Sea.js的配置
  2. Spring Cloud 与 Dubbo 的完美融合之手「Spring Cloud Alibaba」
  3. 如何获取微信API的Access Token
  4. java.security.key jar_异常: java.security.InvalidKeyException: Illegal key size
  5. c#中的奇异递归模式
  6. arcgis server for .NET学习转载5
  7. 批量上传文件及进度显示
  8. HTML通过java信息保存,如何使用java邮件API将HTML格式的数据保存为java邮件的主体?...
  9. Jeecg弱口令后台上传getShell渗透测试
  10. CrownCAD 注册/登录
  11. Table ‘xxx‘ is specified twice, both as a target for ‘UPDATE‘ and as a separate source for data
  12. 谈谈双活业务中心和异地容灾备份设计
  13. 机器学习中的方差与偏差
  14. 2023哈尔滨工业大学计算机考研信息汇总
  15. 华为云与阿里云简要区别
  16. 使用VMD中的Tachyon渲染出透明逼真的水盒子效果
  17. Python PEP—Python增强提案
  18. YYKit Demo
  19. 微服务中统一日志-ELK
  20. 成员信息管理系统c语言,《C语言工会成员信息管理系统》.doc

热门文章

  1. FFMPEG 解码WAV 提取不出数据
  2. 有bug,我不改,哎,就是玩儿!
  3. 教育硕士专业学位(教育管理)研究生阅读书目或期刊目录
  4. Android各个版本名称与版本号对照表
  5. java actioncontext_Struts2 ActionContext 中的数据详解
  6. 合并百度影音的离线数据 with python 2.1 bdv格式的更新
  7. JavaScript split() 方法详解
  8. GprMax2D / 3D——文件下载与入门
  9. 【线性回归】复习笔记
  10. 一文带你从Vue2.x大迈步走进Vue.js 3.0新时代