杂货边角(15):C++11引入的explicit显式转换控制

C++此前编译器默认的隐式类型转换机制,在解决程序的泛型应用上起到了不小的帮助,但是这种隐式的类型转换有可能会导致潜在的问题。举代码如下

#include <iostream>

using namespace std;

struct Rational_1 {
    Rational_1(int n=0, int d=1) : num(n), den(d) {
        cout << __func__ << "(" << num << "/" << den << ")" << endl;
    }
    int num;  //Numerator被除数
    int den;  //Denominator除数
};
struct Rational_2 {
    explicit Rational_2(int n=0, int d=1) : num(n), den(d) {
        //加了explicit关键字,代表该构造函数不能被隐式调用,必须显式调用才能运行
        cout << __func__ << "(" << num << "/" << den << ")" << endl;
    }
    int num;  //Numerator被除数
    int den;  //Denominator除数
};

void Display1(Rational_1 ra){
    cout << "Numerator: " << ra.num << " Denominator: " << ra.den << endl;
}

void Display2(Rational_2 ra){
    cout << "Numerator: " << ra.num << " Denominator: " << ra.den << endl;
}

template <typename T>
class Ptr {
public:
    Ptr(T* p) : _p(p) {}

    //定义一个从Ptr类型转换成bool类型的函数
    explicit operator bool() const {
        if (_p != 0)
            return true;
        else
            return false;
    }
private:
    T* _p;
};
/*************************************************************************
**1. explicit 关键字修饰函数,将会禁止函数被隐式调用
**  有些情况下,函数的隐式调用会导致意想不到的问题出现,
**  经常出现问题的场景有:拷贝构造函数的隐式传递调用 和 非显式的类型转换

*************************************************************************/
int main()
{
    Rational_1 r1_1 = 11;  //该表达式理应是调用拷贝赋值函数,但是因为r1_1并无初始化,所以将隐式调用带参构造函数
    Rational_1 r1_2(12);   //显式调用带参构造函数

    //Rational_2 r2_1 = 21;  //error: conversion from 'int' to non-scalar type 'Rational_2' requested
    Rational_2 r2_2(22);   //显式调用带参构造函数,可以编译通过

    Display1(1);  //参数传递,将隐式调用构造函数
    //Display2(2);  //无法通过编译,could not convert '2' from 'int' to 'Rational_2'
    Display2(Rational_2(2)); //显式调用构造函数传递临时对象

    int a;
    Ptr<int> p(&a);
    if (p)
        cout << "valid pointer." <<endl; //valid pointer
    else
        cout << "invalid pointer." << endl;

    Ptr<double> pd(0);
    cout << p+pd << endl; //如果不加explicit修饰Ptr类中的转换函数,则该表达式将会隐式调用
        //该转换函数,但是指针量之间的相加并无意义,但是因为转换函数的定义,该表达式将被编译通过
        //所以应该给转换函数加上explicit,以避免可能存在不当使用并且难以察觉
        //加上explicit修饰转换函数后,则除了在bool量应该出现的位置,是不会调用该转换函数的
        //所以编译器将报错:error: no match for 'operator+' (operand types are 'Ptr<int>' and 'Ptr<double>'
    return 0;
}

这里写图片描述

此外还需要说明的就是关于操作符operator可以自由定义转换类型的使用情况(operator可以定义不仅仅是+/-/*/bool,还可以是任何其他的类型符合),结合上面的explicit可以给出如下的代码

#include <iostream>

using namespace std;

class ConvertTo {}; //虽然看起来ConvertTo类是空的,但是编译器会为其默认构建一系列的默认构造函数,包括拷贝构造、移动赋值、拷贝赋值等

class Convertable {
public:
    explicit operator ConvertTo () const { return ConvertTo(); } 
    //定义Convertable类型向ConvertTo类型的转换函数
};

void Func(ConvertTo ct) {}

int main()
{
    Convertable c;

    ConvertTo ct(c);  
    //直接初始化,直接调用ConvertTo的拷贝构造函数,因为类Convertable存在显式定义的转换函数,故而
    //将会触发调用ConvertTo::ConvertTo(ConvertTo&)拷贝构造函数,pass

    ConvertTo ct2 = c; 
    //按照格式来看,该表达式应该是触发调用ConvertTo::operator=(const ConvertTo&)拷贝赋值
    //但是因为ct2并无初始化,所以将会隐式触发调用ConvertTo::ConvertTo(ConvertTo&)拷贝构造函数
    //但是此时因为不是显式调用,故而无法显式地调用Convertable的转换函数,从而将会出现
    //error: conversion from 'Convertable' to non-scalar type 'ConvertTo' requested

    ConvertTo ct3 = static_cast<ConvertTo>(c); //强制类型转化,通过


    Func(c);  //could not convert 'c' from 'Convertable' to 'ConvertTo'
    //同样的道理,形参传递,类型不符合理应是调用拷贝构造函数,但是因为并非显式转换,coder没有表达出
    //显式调用转换函数的语义,故而这里将不会触发Convertable类的转换函数
    Func(static_cast<ConvertTo>(c)); //这样子的写法,将‘显式调用类型转换函数’的语义表达清楚了
    //从而可以编译通过

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值