我知道关键字explicit可用于防止隐式转换。
例如
Foo {
public:
explicit Foo(int i) {}
}
我的问题是,在什么条件下,应该禁止隐含转换?为什么隐式转换是有害的?
答案 0 :(得分:11)
如果您希望编译错误,请使用explicit
。
explicit
仅在构造函数中有一个参数时才适用(或许多第一个是没有默认值的参数)。
你可能希望在程序员错误地构造一个对象的任何时候使用explicit
关键字,认为它可能会做一些它实际上没做的事情。
以下是一个例子:
class MyString
{
public:
MyString(int size)
: size(size)
{
}
//... other stuff
int size;
};
使用以下代码可以执行此操作:
int age = 29;
//...
//Lots of code
//...
//Pretend at this point the programmer forgot the type of x and thought string
str s = x;
但是调用者可能意味着在MyString变量中存储“3”而不是3.最好得到一个编译错误,这样用户可以首先在x变量上调用itoa或其他一些转换函数。
将为上述代码生成编译错误的新代码:
class MyString
{
public:
explicit MyString(int size)
: size(size)
{
}
//... other stuff
int size;
};
编译错误始终优于错误,因为它们可以立即显示,以便您进行更正。
答案 1 :(得分:8)
它引入了 意想不到的临时性 :
struct Bar
{
Bar(); // default constructor
Bar( int ); // value constructor with implicit conversion
};
void func( const Bar& );
Bar b;
b = 1; // expands to b.operator=( Bar( 1 ));
func( 10 ); // expands to func( Bar( 10 ));
答案 2 :(得分:5)
一个真实世界的例子:
class VersionNumber
{
public:
VersionNumber(int major, int minor, int patch = 0, char letter = '\0') : mMajor(major), mMinor(minor), mPatch(patch), mLetter(letter) {}
explicit VersionNumber(uint32 encoded_version) { memcpy(&mLetter, &encoded_version, 4); }
uint32 Encode() const { int ret; memcpy(&ret, &mLetter, 4); return ret; }
protected:
char mLetter;
uint8 mPatch;
uint8 mMinor;
uint8 mMajor;
};
VersionNumber v = 10;
几乎肯定会出错,因此explicit
关键字要求程序员输入VersionNumber v(10);
并且 - 如果他或她正在使用体面的IDE - 他们会注意到它需要encoded_version
的智能感知弹出窗口。
答案 3 :(得分:3)
大多数隐式转换是一个问题,当它允许代码编译(并且可能做一些奇怪的事情)时你做了一些你不想做的事情,并且宁愿代码没有编译,而是一些转换允许编译代码并做一些奇怪的事情。
例如,iostreams转换为void *
。如果你有点累,输入类似:std::cout << std::cout;
它会实际编译 - 并产生一些毫无价值的结果 - 通常类似于8或16位十六进制数字(32位系统上的8位数字, 64位系统上的16位数字。)
与此同时,我觉得有必要指出,很多人似乎已经对任何形式的隐性转换产生了几乎反身的厌恶。隐式转换是有意义的类。例如,代理类允许转换为另一种特定类型。转换为该类型对于代理而言永远不会意外,因为它只是一个代理 - 也就是说,您可以(并且应该)认为它完全等同于代理的类型 - 除了当然,为了做任何好事,它必须针对某种特定情况实施一些特殊行为。
例如,几年前我编写了一个bounded<T>
类,它表示一个始终保持在指定范围内的(整数)类型。拒绝分配超出指定范围的值的其他方法,它的行为与底层的intger类型完全相同。它(主要)通过提供对int的隐式转换来实现。几乎你用它做的任何事情,它都会像一个int。基本上唯一的例外是当你为它赋值时 - 如果值超出范围,它将抛出异常。
答案 4 :(得分:2)
对经验丰富的人没有害处。可能对初学者或更新的调试其他代码有害。
答案 5 :(得分:1)
“有害”是一个强烈的声明。 “不经思考就不会使用”是一个很好的选择。很多C ++都是这样的(虽然有些人可能认为C ++的某些部分是有害的......)
无论如何,隐式转换最糟糕的部分是它不仅可以在你不期望它时发生,而且除非我错了,它可以链接......只要在类型Foo之间存在隐式转换路径并键入Bar,编译器将找到它,并沿着该路径转换 - 这可能有许多你没想到的副作用。
如果你获得的唯一一件事就是不必输入几个字符,那就不值得了。明确意味着你知道实际发生了什么,并且不会有所了解。
答案 6 :(得分:0)
要扩展Brian的答案,请考虑一下:
class MyString
{
public:
MyString(int size)
: size(size)
{
}
// ...
};
这实际上允许这段代码编译:
MyString mystr;
// ...
if (mystr == 5)
// ... do something
编译器没有运算符==将MyString与int进行比较,但是它知道如何从一个int中创建一个MyString,所以它会查看if语句,如下所示:
if (mystr == MyString(5))
这是非常误导的,因为看起来它正在将字符串与数字进行比较。实际上,假设MyString(int)构造函数创建一个空字符串,这种类型的比较可能永远不会有用。如果将构造函数标记为显式,则禁用此类转换。因此,请注意隐式转换 - 请注意它允许的所有类型的语句。
答案 7 :(得分:0)
我使用explicit作为转换(单个参数或等效)构造函数的默认选择。当我在一个类和另一个类之间进行转换时,我宁愿让编译器立即告诉我,如果转换是合适的,那么在此时做出决定,或者改变我的设计或实现以完全不需要转换。
对于隐式转换,有害是一个稍微强烈的词。它不仅对初始实现有害,而且对应用程序的维护也有害。隐式转换允许编译器以静默方式更改类型,尤其是参数到另一个函数调用 - 例如自动将int转换为其他对象类型。如果您不小心将int传递给该参数,编译器将“有用”地为您静默创建临时文件,让您在事情无法正常工作时感到困惑。当然,我们都可以说“哦,我永远不会犯这个错误”,但只需要一次调试几个小时才开始考虑让编译器告诉你这些转换是个好主意。