C++ explicit禁止单参数构造函数隐式调用

1.单参数构造函数隐式调用

C++中单参数构造函数是可以被隐式调用的,主要有两种情形会隐式调用单参数构造函数: (1)同类型对象的拷贝构造;即用相同类型的其它对象来初始化当前对象。 (2)不同类型对象的隐式转换。即其它类型对象隐式调用单参数拷贝构造函数初始化当前对象。比如A a=1;就是隐式转换,而不是显示调用构造函数,即A a(1);。像A(1)这种涉及类型转换的单参数构造函数,又被称为转换构造函数(Converting Constructor)。

单参数构造函数的隐式调用示例如下:

#include <iostream>
using namespace std;

class MyInt
{
public:
MyInt( int num)
{
dNum=num;
}
int getMyInt() const
{
return dNum;
}
private:
int dNum;
};

int main()
{
MyInt objMyInt = 10;		//不同类型对象的隐式转换
MyInt objMyInt1=objMyInt;	//同类型对象的拷贝构造,编译器默认生成拷贝构造函数
cout<<objMyInt.getMyInt()<<endl;
cout<<objMyInt1.getMyInt()<<endl;
}

程序输出结果:

10
10

单参数的构造函数在上例中如下两行被调用,

MyInt objMyInt = 10;
MyInt objMyInt1=objMyInt;

这种单参数构造函数被隐式调用在C++中是被默许的,但是这种写法很明显会影响代码的可读性,有时甚至会导致程序出现意外的错误。

2.单参数构造函数隐式调用的危害

单参数构造函数隐式调用不仅仅会给代码可读性造成影响,有时会带来意外的结果。

#include <iostream>
using namespace std;

class MyInt
{
public:
    MyInt(int* pdNum)
    {
        cout<<"in MyInt(int*)"<<endl;
        m_pdNum=pdNum;
    }
    int getMyInt() const
    {
        return *m_pdNum;
    }
    ~MyInt()
    {
        cout<<"in ~MyInt()"<<endl;
        if(m_pdNum)
        {
            delete m_pdNum;
        }
    }
private:
    int* m_pdNum;
};

void print(MyInt objMyInt)
{
    cout<<"in print_MyInt"<<endl;
    cout<<objMyInt.getMyInt()<<endl;
}


int main()
{
    int* pdNum=new int(666);
    print(pdNum);               //意外的被隐式转换为MyInt对象
    int* pdNewNum=new int(888);
    *pdNum=16;
    cout<<*pdNewNum<<endl;      //应该输出888,结果为16
}

程序输出结果:

in MyInt(int*)
in print_MyInt
666
in ~MyInt()
16

程序的本意是想打印输出int指针指向的内容,在没有合适的打印函数被调用时,应该由编译器在编译环节终止编译,报告错误。但是由于编译器“自作主张”的将int指针变量pdNum隐式转换为MyInt对象,调用了函数print(MyInt objMyInt)。objMyInt在函数调用结束后,其生命周期也随之结束,于是其析构函数被调用,导致int指针变量pdNum指向的内容空间被释放。当再次申请int指针变量pdNewNum时,导致pdNewNum与pdNum指向同一块内存空间,于是对pdNum的改写直接影响到pdNewNum,于是出现了上面诡异的结果。

3.explicit禁止单参数构造函数的隐式调用

在没有合适理由必须使用隐式转换的前提下,为了提高代码可读性以及避免单参数构造函数的隐式调用带来的潜在风险,建议使用explicit关键字阻止单参数构造函数的隐式调用。具体做法是在单参数构造函数申明时加上explicit。

class MyInt
{
public:

	explicit MyInt(int num)
	{
		dNum = num;
	}

	explicit MyInt(const MyInt& objMyInt)
	{
		dNum = objMyInt.getMyInt();
	}

	int getMyInt() const
	{
		return dNum;
	}
private:
	int dNum;
};

int main()
{
	MyInt objMyInt = 11;		//编译报错
	MyInt objMyInt1 = objMyInt;	//编译报错
}

当然,多形参调构造函数是没有构造函数的隐式转换,所以没必要声明explicit。


#参考文献 [1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008:1.17explicit的用法 [2]改善C++程序的150个建议[M].李健:提防隐式转换带来的麻烦 [3]深入理解C++中的explicitkeyword

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

Go语言中的Array、Slice、Map和Set使用详解

Array(数组) 内部机制 在 Go 语言中数组是固定长度的数据类型,它包含相同类型的连续的元素,这些元素可以是内建类型,像数字和字符串,也可以是结构类型,元...

35990
来自专栏Java帮帮-微信公众号-技术文章全总结

Java基础-day10-基础题-继承;抽象类

Java基础-day10-基础题-继承&抽象类 什么是继承?继承有什么好处? 继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类...

38160
来自专栏编程心路

你不知道的 equals 和 ==

i1 == i2 和 i1.equals(i2) 这两个都是 true,大多数人应该可以答对。后面的 i3 == i4 和 i3.equals(i4) 估计就...

9320
来自专栏编程

轻松学Python,一篇文章带你快速入门

Python基础01 Hello World! ? Python命令行 假设你已经安装好了Python, 那么在命令提示符输入: python 将直接进入pyt...

21670
来自专栏企鹅号快讯

轻松学习Python:基础知识汇总

Python基础01 Hello World! Python命令行 假设你已经安装好了Python, 那么在命令提示符输入: python 将直接进入pytho...

18880
来自专栏企鹅号快讯

Python类与面向对象

面向对象程序 程序 = 指令 + 数据 代码可以选择以指令为核心或以数据为核心进行编程 两种范例 1.以指令为核心:围绕"正在发生什么"编写 面向过程编程:程序...

27380
来自专栏ImportSource

厕读:每日一题,面试无忧

4. 下列说法正确的有() A. class中的constructor不可省略 B. constructor必须与class同名,但方法不能与class同名 C...

29360
来自专栏余林丰

Java中的Object、T(泛型)、?区别

因为最近重新看了泛型,又看了些反射,导致我对Object、T(以下代指泛型)、?产生了疑惑。 我们先来试着理解一下Object类,学习Java的应该都知道Obj...

274100
来自专栏流媒体

STL(二)map/multimapmapmultimap

Map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据 处理能力。由于这个特...

11930
来自专栏Golang语言社区

Go语言中的Array、Slice、Map和Set使用详解

Array(数组) 内部机制 在 Go 语言中数组是固定长度的数据类型,它包含相同类型的连续的元素,这些元素可以是内建类型,像数字和字符串,也可以是结构类型,元...

79350

扫码关注云+社区

领取腾讯云代金券