本文通过几个例子解释了explicit关键字在构造函数中的作用。

1. 构造函数的隐式转换

C++的默认规定,如果构造函数只接受一个实参,则它实际上定义了转换为此类型的隐式转换机制。(但是只允许一步类类型转换)

// C++ Primer
class Sales_data {
    Sales_data(string s) {}
}

void func(Sales_data a);

int main() {
    Sales_data test("999999");  // 编译通过,"999999"隐式转换为string,再调用构造函数

    func("999999");     // 编译错误,
                        // 1)把"999999"转化为string
                        // 2)把临时string转为Sales_data

    func(string("999999")); // 编译通过,这里把string隐式转换为了Sales_data
}

2. 隐式转换的坑

假设我们要实现一个复数类,然后我们定义了一个Complex类

#include <iostream>

using namespace std;

class Complex
{
private:
    double real;
    double imag;

public:
    // Default constructor
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

    // A method to compare two Complex numbers
    bool operator == (Complex rhs) {
       return (real == rhs.real && imag == rhs.imag)? true : false;
    }
};

int main()
{
    // a Complex object
    Complex com1(3.0, 0.0);

    if (com1 == 3.0)
       cout << "Same";
    else
       cout << "Not Same";
     return 0;
}

// 结果输出Same

很明显,com1 == 3.0此处发生了一次隐式转换,然后3.0转换为了Complex(3.0, 0.0)所以最后输出了Same。

3. explicit作用

如果我们想避开这个坑,那就用explicit来抑制构造函数的隐式转换。

class Complex
{
private:
    double real;
    double imag;

public:
    // Default constructor
    explicit Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

    // A method to compare two Complex numbers
    bool operator== (Complex rhs) {
       return (real == rhs.real && imag == rhs.imag)? true : false;
    }
};

// 此时再运行com1 == 3.0,就会有报错了 
// error: invalid operands to binary expression ('Complex' and 'double')

4.其他例子

class String {
    String(int n) {} //本意是预先分配n个字节给字符串
    String(const char* p) {} // 用C风格的字符串p作为初始化值
}

下面两种写法比较正常:
String s2(10); //OK 分配10个字节的空字符串
String s3 = String(10); //OK 分配10个字节的空字符串

下面两种写法就比较疑惑了:
String s4 = 10; //编译通过,也是分配10个字节的空字符串
String s5 = ‘a’; //编译通过,分配int(‘a’)个字节的空字符串

s4 和s5 分别把一个int型和char型,隐式转换成了分配若干字节的空字符串,容易令人误解。
为了避免这种错误的发生,我们可以声明显示的转换,使用explicit 关键字:

class String {
    explicit String(int n) {} //预先分配n个字节给字符串
    String(const char* p) {} // 用C风格的字符串p作为初始化值
}

加上explicit,就抑制了String(int n)的隐式转换,

下面两种写法仍然正确:
String s2 (10); //OK 分配10个字节的空字符串

下面两种写法就不允许了:
String s4 = 10; //编译不通过,不允许隐式的转换
String s5 = ‘a’; //编译不通过,不允许隐式的转换

所以,explicit可以有效地防止构造函数的隐式转换带来的错误和误解

参考

  1. C++ Primer
  2. http://www.geeksforgeeks.org/g-fact-93/
  3. http://www.cnblogs.com/cutepig/archive/2009/01/14/1375917.html