C++隐式类类型转换及explicit关键字的使用

1. 隐式类型转换

C/C++中基本类型的自动类型转换是经常见到的情形, 例如下边的代码:

int i ;
float j ;
......
j = i;

其实,这就是一种隐式类型转换。当把整形的 i 赋值给浮点型的 j 时,编译器自动完成了转换。 有了隐式类型转换,我们少写了很多代码,省去了不少麻烦。

2. 隐式类类型转换

C++的类可以看作一种用户自定义的数据类型。既然和基本数据类型一样,C++类也是一种数据类型,那么让C++类支持隐式类型转换就成了一种自然而然的想法。

在C++规则中, 可以调用单个实参的构造函数定义了从形参类型到该类类型的一个隐式转换
例如下边的代码:

class Student
{
public: 
    Student() { }
    Student(int age) { }
};

class Teacher
{
public:
    Teacher() { }
    Teacher(int age, string name="unkown") { }  
};

Student类的构造函数仅需要一个实参就能构造对象。Teacher类的构造函数有两个参数,但是由于第二个参数提供了默认值,也可以通过一个参数构造对象。因此,按照C++规则,这两个类的构造函数都实现了隐式转换功能。
所以下边的代码也就完全可以编译通过了:

Student foo;
Teacher bar;

foo = 12;    //隐式转换
bar = 40;    //隐式转换

上述的隐式类型转换同样由编译器完成,编译器通过构造函数构造出一个临时对象,并将其复制为foo和bar。

这样用起来似乎和基本类型的自动转换一样的爽快, 省去了好多代码。然而,凡事总是有两面性,越是便利性的东西,往往越容易犯错。你怎么能够总是保证编译器帮你构造出来的代码就是你想要实现的逻辑呢?一旦出现代码拼写错误,代码实现和自己的逻辑不符,而编译器又不报错,Debug时将加倍地费时费力。

关于隐式类类型转换的缺点,网上流传比较广泛的是如下的两个例子:

例1:拼写错误
Array<int> a[10];
Array<init>b[10];
for(int i=0;i<10;i++) {
     if(a==b[i]) {        //原意是a[i],现在出现了错误
                              //发生点什么
      }
}

这个例子中省略了一部分代码,原文请参阅参考文档[1].

简单讲,由于Array类中定义了一个可以只接收一个整形参数的构造函数, 因此在if(a==b[i])发生拼写错误的情况下, 编译器并不会报错,而是自动的从b[i]隐式转换出一个Array类型的临时对象,并同a进行比较。

例2:逻辑错误
// A simple class
class A {};

// Another simple class with a single-argument constructor for class A
class B
{
public:
    B() {}
    B(A const&) {}
};

// A function that expects a 'B'
void f(B const&) {}

int main()
{
    A obj;
    f(obj); // Spot the deliberate mistake
}

这个例子说逻辑错误似乎有点牵强,因为有人会讲,我本来的逻辑就是要通过A类的对象构造出来一个B类的对象,然后给f()函数使用。好吧,如果是这样,我们至少可以说逻辑不够严谨吧。至少大部分读代码的人会感到诧异,我们要让f()函数要处理的是一个B类对象,但是却给了它一个A类的对象作为参数,而且代码编译是完全没有问题的。

之所以代码编译不出问题是因为B类包含了一个可以接受A类对象引用值作为参数的构造函数,满足了隐式转换规则。所以当我们把A类对象obj作为参数送给f()函数时,编译器调用了隐式转换规则,生成了一个临时的B类对象并传递给f()函数。上述例子的完整表述,可以参阅参考文档[2].

你可能会想,隐式转换也太强大了吧,问题是,你是否能够保证每次都是你有意写出的代码,而不是无心插柳?

3. Google C++编程规范对隐式类型转换的建议

关于隐式类型转换的优缺点,Google C++编程规范有详细的描述。 其优点就是语法简洁,省事儿。缺点就比较多了:

  • 容易隐藏类型不匹配的错误;
  • 代码更难阅读;
  • 接收单个参数的构造方法可能会意外地被用做隐式类型转换;
  • 不能清晰地定义那种类型在何种那个情况下应该被隐式转换,从而使代码变得晦涩难懂。

Google的结论就是可接收单个参数的构造函数必需要加上explicit标记,禁止隐式类类型转换 (复制和移动构造函数除外,因为这两者不执行类型转换)

按照Google编程规范,上述例子中可接收单个参数的构造函数都应该声明为如下形式。

class Student
{
public: 
    Student() { }
    explicit Student(int age) { }
};

class Teacher
{
public:
    Teacher() { }
    explicit Teacher(int age, string name="unkown") { } 
};

这样将会禁止类类型的隐式转换,编译器在编译类似代码时会直接报错,提醒开发人员检查自己的代码是否符合逻辑。

参考文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容