有关转型的几种做法,已经在早些的博客中写过了。这里先简单回顾一下,再讲一讲effective中对之更深入的阐述。

转型可以按风格可以分成C风格转型和C++风格转型两大类,C风格转型很容易看到,因为我们会经常使用,像

(T) expression

以及:

T (expression)

最经典的例子就是处理整数除法,在C/C++程序中,整数除法的结果还是整数,有时会得不到我们想到的结果,比如3/5,结果是0,而不是0.6,但如果这样double(3) / 5,结果就会是0.6了,因为转型操作double(3),将整数转成了浮点数,这样就是小数除法了,可以得到带小数点的结果。

C++风格的转型操作分成四类:

const_cast<T>(expression)
static_cast<T>(expression)
dynamic_cast<T>(expression)
reinterpret_cast<T>(expression)

effective推荐使用的是前三种,最后一种是把一个指针转成整数或者把整数看成指针,对平台依赖性很强,不建议使用。

const_cast很简单,就是去掉常量属性,比如:

 int main()
{
int a= ;
const int* ca = &a;
int *pb = const_cast<int*> (ca);
*pb = ;
cout << a << endl; // a的输出结果是5
}

但这种常量性的转换只是针对于指针或引用(包括this指针),是不能针对于普通变量的,比如下面的做法就是错误的:

 int main()
{
const int a = ;
int b = const_cast<int>(a); //编译出错,只能使用static_cast或者C风格转换
}

对于const_cast,既可以将const->非const,也可以将非const->const,只要记住两点,其一是这个操作只对指针或引用有效;其二,这个操作并不改变被转换对象的常属性,对于:

 int a= ;
const int* ca = &a;
int *pb = const_cast<int*> (ca);

ca仍是指向常量的指针,对它的操作,比如*ca = 5,是会报编译错的。

static_cast是最为常用C++转型了,我们常见的int->double亦或是float->int等等,都是用static_cast来进行转换的(包括const int -> int,以及int -> const int,只要不是常指针/引用->non常指针/引用)。

dynamic_cast是当想把基类指针转成派生类指针时用,这种转换可以保证安全性,当把两个不相干的类之间用dynamic_cast时,转换的结果将是空指针,同时若基类指针指向的对象只是基类本身时,对基类指针进行dynamic_cast向下转型到派生类,会得到空指针,防止进一步的操作。

介绍到这里,需要我们回答一个问题,既然旧式转型已经够用了(毕竟C语言就是这样用的,没有人抱怨转型的功能不够丰富),那为什么C++还要再定义功能重复的转型呢?

原因有两个:第一,细化转型使它们很容易在代码中被辨识出来,可以不去看上下文就知道这样的转型是什么类型的(常量转型还是向下转型);第二,各转型动作的目标愈窄化,编译器愈可能诊断出错误的应用。

下面来讲一下,为什么effective中要我们尽量少做转型动作。

不清楚原理的转型会带来严重的bug,比如书上提到的,假设有一个基类Window,它有一个派生类SpecialWindow,它们都有一个OnResize()的成员函数,在派生类的OnResize()中想要先调用基类的OnResize(),于是有程序员便这样做了:

 void SpecialWindow::OnResize()
{
static_cast<Window>(*this).OnResize();

}

这样真的没有问题吗?将派生类对象转成Window对象,然后对其调用OnResize(),这确确实实调用的是基类的OnResize(),但OnReszie()对成员变量操作的结果是你想要的吗?

答案是否定的,因为static_cast生成的是一个临时的基类的对象,这个对象并不是真正组成派生类的那个基类,而是它的一个副本(这个副本的成员变量值与派生类对象中基类的成分是一样的,但地址不同),调用OnResize()变更的结果是这个副本的成员变量变了,但派生类中包含的基类的成员变量却是没有任何变化的。好了,这就是bug了,在之后的测试中这个问题会让程序员纠结好一阵子。

一句话,转型生成的是一个copy,一份副本,有的时候这并不是你想要的,修正方法其实很简单,就是:

 void SpecialWindow::OnResize()
{
Window::OnResize(); // OK了,就这么简单

}

有的转型操作也是比较废的,比如dynamic_cast,这个转型会对类名称进行strcmp,以判断向下转型是否合理,如果继承深度比较大,那么每一次的dynamic_cast将会进行多次strcmp,这将严重影响程序的执行效率。解决方法就是如果可以话,直接使用指向子类的指针,真的想用父类的指针(比如工厂设计模式等),那就考虑多态吧,在父类相应的函数前面加virtual,然后子类进行覆盖即可。

最后总结一下,有的程序员认为,转型其实什么也没有做,只是告诉编译器把某种类型视为另一种类型而已。如果说的是指针,这样理解是正确的,但如果说的是全部的话(包括变量),恐怕就不妥了,比如static_cast<double>(a) / b,这一类经典的整数除法->小数除法转换,转换前后在底层产生的代码是绝对不同的。

1. 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。

2. 如果转型是必要的,试着将它隐藏于某个函数背后,客户可以随后调用这个函数,而不需要将转型放在他们自己的代码里。

3. 宁可使用C++风格的新式转型,少用C风格转型,因为前者很容易辨识出来,而且也比较有着分门别类的职掌。

读书笔记_Effective_C++_条款二十七:尽量少做转型动作的更多相关文章

  1. 读书笔记_Effective_C++_条款二:尽量以const, enum, inline替换#define

    其实这个条款分成两部分介绍会比较好,第一部分是用const和enum替换不带参的宏,第二部分是用inline替换带参的宏. 第一部分:用const和enum替换不带参宏 宏定义#define发生在预编 ...

  2. Effective C++ -----条款27:尽量少做转型动作

    如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts.如果有个设计需要转型动作,试着发展无需转型的替代设计. 如果转型是必要的,试着将它隐藏于某个函数背后.客户随后可以调用该 ...

  3. 条款27:尽量少做转型动作(Minimize casting)

    NOTE : 1.如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts. 如果有个设计需要转型动作,试着发展无需转型的替代设计. 2.如果转型是必须要的,试着将它隐藏于某个函 ...

  4. 读书笔记_Effective_C++_条款四十七:请使用trait classes来表示类型信息

    这一条款主要来讨论模板中迭代器的属性iterator_category,它可以通过类似于vector<int>::iterator::iterator_category的方式来取得. 到这 ...

  5. 读书笔记_Effective_C++_条款二十四: 若所有参数皆需类型转换,请为此采用non-member函数

    class A { private: int a; public: A(int x) :a(x){} A operator*(const A& x) { return A(a*x.a); } ...

  6. 读书笔记_Effective_C++_条款三十七:绝不重新定义继承而来的缺省参数值

    先看下面的例子: enum MyColor { RED, GREEN, BLUE, }; class Shape { public: ; }; class Rectangle: public Shap ...

  7. 读书笔记_Effective_C++_条款二十九:为“异常安全”而努力是值得的

    还是举书上的例子: void PrettyMenu::changeBackground(std::istream& imgSrc) { lock(&mutex); delete bgI ...

  8. 读书笔记_Effective_C++_条款二十八:避免返回handlers指向对象内部成分

    举个例子: class Student { private: int ID; string name; public: string& GetName() { return name; } } ...

  9. 读书笔记_Effective_C++_条款二十六:尽可能延后变量定义式的出现时间

    这个条款从字面意思还是很好理解的,就是在使用这个变量前才去定义,而不是很早就定义了它,而在很后面的时候才去使用.这个条款只适用于对变量声明位置没有要求的语言,比如C++.对于像C或者一些脚本语言,语法 ...

随机推荐

  1. java正则: 忽略大小写匹配

    import java.util.regex.Matcher; import java.util.regex.Pattern; import com.sun.org.apache.xerces.int ...

  2. KKT条件和拉格朗日乘子法详解

    \(\frac{以梦为马}{晨凫追风}\) 最优化问题的最优性条件,最优化问题的解的必要条件和充分条件 无约束问题的解的必要条件 \(f(x)\)在\(x\)处的梯度向量是0 有约束问题的最优性条件 ...

  3. [MySQL] gap lock/next-key lock浅析

    当InnoDB在判断行锁是否冲突的时候, 除了最基本的IS/IX/S/X锁的冲突判断意外, InnoDB还将锁细分为如下几种子类型: record lock (RK) 记录锁, 仅仅锁住索引记录的一行 ...

  4. 62.Unique Paths---dp

    题目链接 题目大意:给一个m*n的方格,从左上角走到右下角,中间无任何障碍,问有多少种走法. 法一:DFS,超时,简单模板深搜,无任何剪枝,结果一半的数据超时.代码如下: public int uni ...

  5. <mvc:annotation-driven/>都做了那些事情

    mvc:annotation-driven是一种简写的配置方式,那么mvc:annotation-driven到底做了哪些工作呢?如何替换掉mvc:annotation-driven呢? <mv ...

  6. 斐讯路由器L(联)B(壁)K-码兑换包安全下车通道(图文教程)

    大家好,最近大家比较关心的斐讯路由器如何下车问题,楼主亲自试提取了一遍,记录下过程,欢迎大家一起讨论. 言归正传,上图,上图! No.1 打开斐讯提供的良心k码退换通道: https://tech-s ...

  7. MYSQL三种安装方式--rpm包安装

    1. 首先检查机器里是否已经存在MySQL $ rpm -qa | grep mysql 2. 去官网下载相应的rpm包:https://dev.mysql.com/downloads/mysql/ ...

  8. Mybatis Common Mapper文件

    表名/条件/字段 都可以传入进去 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mappe ...

  9. Java学习(基本语句,语法,变量)

    一.基本语法: public class Demo { //定义一个类 public static void main(String[] args) { //主方法,一切程序的起点 /* 在屏幕上打印 ...

  10. DotNetOpenAuth实践之WebApi资源服务器

    系列目录: DotNetOpenAuth实践系列(源码在这里) 上篇我们讲到WCF服务作为资源服务器接口提供数据服务,那么这篇我们介绍WebApi作为资源服务器,下面开始: 一.环境搭建 1.新建We ...