C++隐式转换构造函数与explicit关键字

explicit研究

explicit是C++中的关键字,不是C语言中的。英文直译是“明确的”、“显式的”意思。出现这个关键字的原因,是在C++中有这样规定的基础上:当定义了只有一个参数的构造函数时,同时也定义了一种隐式的类型转换。先看类型转换。

隐式转换的场景

等于号与构造函数
比如你有一个类的对象A:

class A
{
public:
    A(int i)
    {
        a = i;
    }
    int getValue()
    {
        return a;
    };
private:
    int a;
};

你会发现,你在main函数中,使用下面的语句时是合法的:
A a;
a  = 10;
之所以类A的对象可以直接使用整型通过等于号来初始化,是因为这一语句调用了默认的单参数构造函数,这种构造函数又称 类型转换构造函数。其效果等价于A temp(10); a(temp);
首先编译器执行A temp(10);在栈中创建了一个临时对象(假设叫做temp)。然后再调用对象a的拷贝初始化构造函数 a(temp) 给a初始化。然后临时对象temp销毁。这就是编译器做的隐式转换工作。你可以想到这样的隐式操作的结果和直接显示调用A a(10);的结果是一样的,但是隐式转换因为使用了拷贝构造函数所以在开销上会更高一些。当然这基本数据类型,或许不明显。如果一个复杂的对象,比如Qt的窗口。那么开销可想而知。
注意当你使用A a = 10;时并不会产生中间的临时对象。而是直接把10作为参数传递给类型转换构造函数。
又如当你使用如下语句会不通过:
A a = "123";
因为没有参数为字符串的单参数构造函数。知道了这个,你修改一下就能通过了。

class A
{
public:
    A(int i)
    {
        a = i;
    }
    A(char * c)
    {
        a=c[0];
    }
    int getValue()
    {
        return a;
    };
private:
    int a;
};

函数调用
我们再定义一个函数print 用来打印A对象的值。

void print(A a)
{
    cout<<a.getValue();
};
在main函数中:
void main()
{
    print(10);
}


这样是可以编译运行的。虽然我们并没有创建一个类A的对象来传给print 函数。但是编译器默认会调用类A的单参数构造函数,创建出一个类A的对象出来。


加上explicit
上面可以看出编译器会为你做一些,隐式的类型转换工作。这或许会让你感到方便,但是有时候却会带来麻烦。我们来做一个假设:
上面这个类A,只接受整型和字符串型。所以你想传递一个字符串 “2” 作为参数来构造一个新的对象。比如 A b = “2”; 然而,你却写错了,双引号写成了单引号变成了 A b = ‘2’; 然而这样并不会报错。编译器 会把 字符 ‘2’ 转型成 整型 也就是它的ascll码—— 50。为了避免这样我们不希望的隐式转换,我们可以加上explicit 关键字。

public:
    explicit A(int i)
    {
        a=i;
    }

这样就能避免隐式的类型转换了,当你误写成单引号的时候,就会报错。这样就只允许显示的调用单参数构造函数了。如 A a(10); A b("123"); 
不仅如此,在加上explicit之后,print函数也会报错了。因为编译器不会主动调用explicit标识的构造器。这就需要你自己显示的来调用了:
print(A(10));
当然了,是否应该禁止隐式转换是没有定论的,没有一种放之四海皆准的标准,具体看你的情景需要了。
一般而言,显示调用构造器,能避免一些麻烦,让程序员手动来管理。很多人说C++难,因为很多东西对于程序员来说不是透明的,比如内存释放什么的,这个显式调用也是需要程序员自己动手的。然而我感觉这正是C++的魅力所在,C++给了程序员几乎等同于上帝的权力,所有一切都能自己掌控,还比如运算符重载的权力。
————————————————
 

C语言有简单粗暴的强制类型转换,当前C++也有,问,可以将一个数字强制转换为一个类吗?

class TestCls {
public:
    int a;
};

int main(void) {
    TestCls t = 10;
    return 0;
}

这样编译肯定有编程报错:

这里写图片描述

加上强制类型转换?

TestCls t=(TestCls)10;

编译:

这里写图片描述

  提示说找不到TestCls::TestCls(int),这显然是个构造函数。定义这么一个构造函数后:

class TestCls
{
public:
    int a;

    TestCls(int a) {}
};

编译就通过了。

TestCla(int a)函数在这里是一个转换构造函数,转换构造函数的作用就是把传入的其他类型参数转换成类的对象。转换构造函数说白了还是一个构造函数,只不过该构造函数:

(1)有且只有一个参数

(2)参数是基本类型或者是其他类类型,也就是说不可以是本身的类型

 

在有转换构造的类TestCls中,我们可以不用加上强制类型转换,同样能通过编译,即:

TestCls t = 10;

那么执行这句代码时,编译器做了什么?

10这个立即数默认int类型,它被用来初始化一个类对象,显然不符合逻辑,那么编译器会先查找class中是否有int类型参数的转换构造函数,即找到TestCls(int a), 那么该行代码将会变成:

TestCls t=TestCls(10);

即编译器会调用这个转换构造函数,生成一个临时对象进而初始化对象t,这是由于编译器具有隐式类型转换功能,它会尽力尝试让程序编译通过。

     但是在实际项目工程中,可能我们对于 TestCls t =10; 这样把述职直接初始化对象只是一个误操作,将10初始化对象t并非程序员本意,所以我把要去除这个编译器隐式转换的功能,这就需要explicit 关键字修饰转换构造函数。

class TestCls
{
public:
    int a;
    explicit TestCls(int a)  {}
};

修改后再次编译:

这里写图片描述

编译器不会进行隐式转换,倘若程序员还是想要实现将10初始化对象t,那就可以使用强制类型转换:

TestCls t = (TestCls)10;

或者

TestCls t = static_cast<TestCls>(10)

参考:

https://blog.csdn.net/guodongxiaren/article/details/24455653

https://blog.csdn.net/qq_29344757/article/details/77069546

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值