从 C 向 C++ 进阶系列导航
1. 类类型隐式转换陷阱
C++ 的类类型的转换规则如下:
- 转换函数定义在源对象(转换对象)类中,是转换源对象的成员函数。
- 一旦为转换源类型提供了到目标类型的转化函数,就可以将源类对象以隐式转化的方式转换得到目标类型对象。
- 当没有定义转换函数时,编译器会尽力把源对象转化为目标对象,且容易触发隐私转换,不一定报错。
- 实验:
class Test
{
private:
int m_var;
public:
Test(int num)
{
m_var = num;
}
int GetVar()
{
return m_var;
}
};
int main(int argc, char *argv[])
{
Test obj_A = 0;
obj_A = 2;
cout << "obj_A.GetVar() = " << obj_A.GetVar() << endl; // obj_A.GetVar() = 2
obj_A = 3.14;
cout << "obj_A.GetVar() = " << obj_A.GetVar() << endl; // obj_A.GetVar() = 3
}
以上在将整形字面量 2 和浮点型字符量 3.14 分别赋值给类对象 obj_A 时,编译器会尽力地进行类型转,例如:
obj_A = 2; => obj_A = Test(2);
obj_A = 3.14; => obj_A = Test(3.14);
以上的构造函数又称为转换构造函数。当然,这样的隐式类类型转换是不安全的,因为程序编译并没有报错,但程序的结果却未必是所期望的。
2. explicit 关键字
explicit 关键字用来声明构造函数,表示编译器不能自动进行隐式类型转换,需要在程序中进行显式地声明类型转换。
- 实验:
class Test
{
private:
int m_var;
public:
explicit Test(int num)
{
m_var = num;
}
int GetVar()
{
return m_var;
}
};
int main(int argc, char *argv[])
{
// Test obj_A = 1; // error: conversion from ‘int’ to non-scalar type ‘Test’ requested
Test obj_A(1);
Test obj_B = static_cast<Test>(1);
cout << "obj_B.GetVar() = " << obj_B.GetVar() << endl; // obj_B.GetVar() = 1
obj_B = static_cast<Test>(2);
cout << "obj_B.GetVar() = " << obj_B.GetVar() << endl; // obj_B.GetVar() = 2
}
需要注意的是,在使用 explicit 关键字声明后,在类的初始化时应使用括号形式进行赋值,否则必须显式声明转换类型。
3. operator 关键字
explicit 关键字用来声明成员函数,使其作为类型转换函数。类型转换函数可以将类对象转换为其它类型,函数形态为:
operator Type()
{
...
return obj;
}
其中,Type 为需要转换的目标类型,obj 为转换的目标类型对象。特殊地,类型转换函数是没有参数与返回类型的,因为参数与返回类型都被隐藏在函数体内。
- 实验:
class Test_A
{
private:
int m_var;
public:
Test_A(int num)
{
m_var = num;
}
int GetVar()
{
return m_var;
}
friend class Test_B;
};
class Test_B
{
private:
int m_var;
public:
Test_B(int num)
{
m_var = num;
}
int GetVar()
{
return m_var;
}
operator Test_A()
{
Test_A obj = 0;
obj.m_var = m_var;
return obj;
}
};
int main(int argc, char *argv[])
{
Test_A obj_A = 1;
cout << "obj_A.GetVar() = " << obj_A.GetVar() << endl; // obj_A.GetVar() = 1
Test_B obj_B = 2;
cout << "obj_B.GetVar() = " << obj_B.GetVar() << endl; // obj_B.GetVar() = 2
Test_A obj_C = obj_B;
cout << "obj_C.GetVar() = " << obj_C.GetVar() << endl; // obj_C.GetVar() = 2
}
4. 类类型转换的冲突
类型转换函数可能会与转换构造函数相冲突。
- 示例:
class Test_B;
class Test_A
{
private:
int m_var;
public:
Test_A(int num)
{
m_var = num;
}
Test_A(Test_B& obj);
int GetVar()
{
return m_var;
}
friend class Test_B;
};
class Test_B
{
private:
int m_var;
public:
Test_B(int num = 0)
{
m_var = num;
}
int GetVar()
{
return m_var;
}
operator Test_A()
{
Test_A obj = 0;
obj.m_var = m_var;
return obj;
}
};
Test_A::Test_A(Test_B& obj)
{
m_var = obj.GetVar();
}
int main(int argc, char *argv[])
{
Test_B obj_B(1);
// Test_A obj_A = obj_B; // error : conversion from ‘Test_B’ to ‘Test_A’ is ambiguous class Test_B’
cout << "obj_A.GetVar() = " << obj_A.GetVar() << endl;
}
编译器不知道到底该调用转换构造函数还是类型转换函数,而解决方法就是在声明 Test_A(Test_B& obj);
前加上 explicit 关键字防止转换构造函数发生隐式转换。
在实际工程中,往往使用形为 Type toType()
的函数来代替类型转换函数。例如转换为整形的函数形为:
int toInt()
{
...
}