C++中的转换函数、explicit关键字与non-explicit-one-argument构造函数

C++中提供了转换函数,用于将本类对象转换成另一类型的对象。其语法为

class CTestClass
{
    operator 待转换类型() const 
    {
      //函数体
      return (待转换类型)xxx;
    }
}

从中可以看出:

  1. 该转换函数不需要指明返回值类型(已经在operator后说明了)
  2. 该函数被标记为const,说明其不改变成员变量(毕竟是转换而已)
  3. 该函数无参。
  4. 一个类可有多个转换函数,将其转换到不同类型。

一个简单的例子

下面举一个最简单的使用例子:

#include <iostream>
using namespace std;

class CFraction
{
private:
    int m_upper;
    int m_lower;

public:
    CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }

    double GetValue() const
    {
        return (double)m_upper / (double)m_lower;
    }

    operator double() const
    {
        return GetValue();
    }
};

int main()
{
    CFraction f1(3, 4);
    double d = 4.0 + f1;    
    cout << d << endl;  //结果为4.75
    return 0;
}

在上例中,当编译到double d = 4.0 + f1一句时,编译器将做如下尝试:

  1. 有没有重载+运算符,其参数从左到右分别为double和CFraction类型。很可惜,这里没有。

  2. 能否考虑把CFraction类型的f1转换为double类型,然后参与运算。于是这里CFraction::operator double() const函数就派上用场了。

当+运算符与转换函数同时可用时

在上面例子的基础上,我们稍加改动,加入一个全局的重载+运算符的函数。

#include <iostream>
using namespace std;

class CFraction
{
private:
    int m_upper;
    int m_lower;
public:
    CFraction(int upper, int lower);
    double GetValue() const;
    operator double() const;
};

double operator+ (const double opr1, const CFraction& opr2)
{
    return opr1 + opr2.GetValue() + 22;
}

CFraction::CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }

double CFraction::GetValue() const
{
    return (double)m_upper / (double)m_lower;
}

CFraction::operator double() const
{
    return GetValue();
}

int main()
{
    CFraction f1(3, 4);
    double d = 4.0 + f1;
    cout << d << endl;  // 结果为26.75
    return 0;
}

很明显,这个例子说明在二者都能行得通的前提下,编译器优先选择了执行全局重载的+运算符。

explicit关键字的加入

在上面的例子中,我们注意到CFraction类的构造函数有如下特点:

  1. 最少只需要一个参数就能构造出CFraction的对象。
  2. 没有使用explicit关键字修饰。

基于以上特点,给其取名“non-explicit-one-argument ctor”。在这种情况下,我们对main函数中的代码进行如下修改:

#include <iostream>
using namespace std;

class CFraction
{
private:
    int m_upper;
    int m_lower;
public:
    CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }
    CFraction operator+ (const CFraction& rhs) const
    {
        return CFraction(m_upper*rhs.m_lower + m_lower*rhs.m_upper, m_lower * rhs.m_lower);
    }
    int GetUpper() const { return m_upper; }
    int GetLower() const { return m_lower; }
};

ostream& operator<< (ostream& os, const CFraction& rhs)
{
    os << rhs.GetUpper() << "//" << rhs.GetLower();
    return os;
}

int main()
{
    CFraction f1(3, 4);
    CFraction d = f1 + 4;
    cout << d << endl;  // 结果显示为“19//4”
    return 0;
}

下面来分析一下CFraction d = f1 + 4对应编译器的行为:首先编译器查找CFraction类型有没有重载运算符+号且返回为CFraction类型的,于是找到了CFraction operator+ (const CFraction& rhs) const函数,然后编译器又尝试将4转换为 CFraction类型,这时候,这个精心设置的构造函数就派上用场了!

编译器会在栈上开辟一个CFraction类型的临时对象大小的空间,并调用其non-explicit-one-argument ctor,传入4,生成该临时对象,参与运算后返回。弄明白了这点后,就不难说明,将代码改成下面这样为什么能顺利编译运行了:

#include <iostream>
using namespace std;

class CFraction
{
private:
    int m_upper;
    int m_lower;
public:
    CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }
    double operator+ (const CFraction& rhs) const
    {
        return ((double)m_upper*rhs.m_lower + m_lower*rhs.m_upper / (double)m_lower * rhs.m_lower);
    }
    int GetUpper() const { return m_upper; }
    int GetLower() const { return m_lower; }
};

ostream& operator<< (ostream& os, const CFraction& rhs)
{
    os << rhs.GetUpper() << "//" << rhs.GetLower();
    return os;
}

int main()
{
    CFraction f1(3, 4);
    CFraction d = f1 + 4;
    cout << d << endl;  // 结果为4//1
    return 0;
}

要解释原因也很简单,编译器找到了double operator+ (const CFraction& rhs) const函数后,发现可以将4转换成 CFraction,又可以把得到的double型结果先转换为int型,然后扔给non-explicit-one-argument ctor生成一个CFraction的对象。

稍微总结一下,不难得出:non-explicit-one-argument类型的构造函数可能被编译器隐式调用,进行类型转换。如果我们再在上面的例子上加以改动:

#include <iostream>
using namespace std;

class CFraction
{
private:
    int m_upper;
    int m_lower;
public:
    CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }
    double operator+ (const CFraction& rhs) const
    {
        return ((double)(m_upper*rhs.m_lower + m_lower*rhs.m_upper) / (double)(m_lower * rhs.m_lower));
    }
    int GetUpper() const { return m_upper; }
    int GetLower() const { return m_lower; }

    double GetValue() const
    {
        return (double)m_upper / (double)m_lower;
    }

    operator double() const
    {
        return GetValue();
    }
};

ostream& operator<< (ostream& os, const CFraction& rhs)
{
    os << rhs.GetUpper() << "//" << rhs.GetLower();
    return os;
}

int main()
{
    CFraction f1(3, 4);
    double d = f1 + 4;  //此处报错,编译无法完成
    cout << d << endl;  
    return 0;
}

这样终于把聪明的编译器考倒了,因为此时针对double d = f1 + 4两条路(隐式转换将4转换为CFraction类型对象后参与运算或者将f1转换为double类型后参与运算),于是编译器懵逼了,报错罢工!这时候,如果我们不想让编译器帮我们隐式转换生成CFraction对象(即分配栈内存后调用non-explicit-one-argument ctor),explicit关键字就派上用场了:

#include <iostream>
using namespace std;

class CFraction
{
private:
    int m_upper;
    int m_lower;
public:
    explicit CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }
    double operator+ (const CFraction& rhs) const
    {
        return ((double)(m_upper*rhs.m_lower + m_lower*rhs.m_upper) / (double)(m_lower * rhs.m_lower));
    }
    int GetUpper() const { return m_upper; }
    int GetLower() const { return m_lower; }

    double GetValue() const
    {
        return (double)m_upper / (double)m_lower;
    }

    operator double() const
    {
        return GetValue();
    }
};

ostream& operator<< (ostream& os, const CFraction& rhs)
{
    os << rhs.GetUpper() << "//" << rhs.GetLower();
    return os;
}

int main()
{
    CFraction f1(3, 4);
    double d = f1 + 4;
    cout << d << endl;  // 结果为26.75
    return 0;
}

这样编译器由于知道不能隐式转换出CFraction类,就只好老老实实去调用转换函数了。总结一下:

  1. 严格意义上的转换函数指的是转出函数,即将本类型的对象转换为其他类型对象的函数。这类函数的语法是:
    operator 转出类型() const { //… return (转出类型)xxx; }
  2. 广义上的转换函数其实还包括non-explicit-one-argument ctor,该类型的构造函数只需要接收一个参数即可。用于将将收到的参数类型对象转换成本类型对象。当编译器判定可以进行隐式转换的时候,会创建临时对象,自动生成调用non-explicit-one-argument ctor的代码。
  3. 如果不希望编译器对本类型进行隐式转换,则需在本类型的non-explicit-one-argument ctor前,加上explicit关键字。
  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值