构造函数中使用explicit关键字的作用

15

我试图理解c++中explicit关键字的用法,并查看了SO上的这个问题What does the explicit keyword mean in C++?

然而,那里列出的示例(实际上是前两个答案)在使用方面并不是很清楚。例如:

// classes example
#include <iostream>
using namespace std;

class String {
public:
    explicit String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};

String::String(int n)
{
    cout<<"Entered int section";
}

String::String(const char *p)
{
    cout<<"Entered char section";
}

int main () {

    String mystring('x');
    return 0;
}

现在我已经将字符串构造函数声明为explicit,但是假设如果我不将其列为显式的,如果我这样调用构造函数:

String mystring('x');

OR
String mystring = 'x';

在这两种情况下,我都将进入int部分。除非我指定值的类型,否则它默认为int。即使我使用参数更加具体,例如将其中一个声明为int,另一个声明为double,并且不使用构造函数名称的显式调用方式,也是如此。

String mystring(2.5);

或者这样。
String mystring = 2.5;

它将始终默认使用双精度作为参数的构造函数。因此,我很难理解explicit的真正用途。你能提供一个不使用explicit会导致真正失败的例子吗?


2
你期望 String('x') 调用 String(const char*) 吗? 'x' 是一个 char,而不是 char 指针。无论如何,你只展示了 String显式 构造,所以 explicit 没有任何区别。尝试调用一个带有 intString 函数,它将无法工作,因为转换 intString 不能 隐式 进行。 - Biffen
String mystring = 'x'; 不起作用 - cpplearner
@Biffen,你能否用一个示例代码来扩展你的论点? - SandBag_1996
@UnderDog 是哪个参数?如果你的意思是调用一个需要 String 的函数,那么请参考 Jerry Coffin 的回答。 - Biffen
@UnderDog 不使用 explicit 不会 失败,但可能会导致不期望的行为。 - Biffen
显示剩余4条评论
1个回答

13

explicit的作用是防止隐式转换。任何时候您使用类似于String(foo);这样的内容,那都是一个显式转换,因此使用explicit不会改变其成功或失败的情况。

因此,让我们看一个涉及隐式转换的场景。让我们从您的String类开始:

class String {
public:
    explicit String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};

那么我们定义一个函数,该函数接收一个 String 类型的参数(也可以是 String const &,但目前 String 就足够了):

int f(String);

你的构造函数允许从char const *进行隐式转换,但仅允许从int进行显式转换。这意味着如果我调用:

f("this is a string");

编译器将生成代码,从字符串文字构造一个String对象,然后用该String对象调用f函数。

但如果您尝试调用:

f(2);

这将失败,因为接受一个int参数的String构造函数被标记为explicit。这意味着如果我想将int转换为String,我必须明确地执行:

f(String(2));
如果String(char const*)构造函数也被标记为explicit,那么你将无法调用f("this is a string"),而需要使用f(String("this is a string"));。请注意,explicit仅控制从某种类型foo到您定义的类型的隐式转换,对于从其他类型您的explicit构造函数所接受的类型的隐式转换没有影响。因此,接受类型int的显式构造函数仍将接受浮点参数。
f(String(1.2))

因为这涉及到从doubleint的隐式转换,然后是intString的显式转换。如果您想禁止从doubleString的转换,可以通过提供接受double参数的重载构造函数并抛出异常来实现。

String(double) { throw("Conversion from double not allowed"); }

现在,从doubleint的隐式转换将不会发生——double将直接传递给您的构造函数而不进行转换。

至于使用explicit实现的目标:使用explicit的主要目的是防止本应该编译的代码被编译。当与重载结合使用时,隐式转换有时可能会导致一些相当奇怪的选择。

更容易演示转换运算符的问题而不是构造函数(因为可以仅使用一个类)。例如,让我们考虑一个非常类似于人们在理解隐式转换存在问题之前编写的字符串类:

class Foo {
    std::string data;
public:
    Foo(char const *s) : data(s) { }
    Foo operator+(Foo const &other) { return (data + other.data).c_str(); }

    operator char const *() { return data.c_str(); }
};

(我使用了std::string来存储数据,这其实是在偷懒,但是如果像他们一样使用char *来存储,并使用new来分配内存,结果也是一样的。)

现在,这个方法可以很好地运行:

Foo a("a");
Foo b("b");

std::cout << a + b;

……而且,(当然)结果是输出ab。但如果用户在打算键入+时打成了-会发生什么?

这时情况就变得丑陋了——代码仍然能够编译和“运行”(某种程度上),但输出的是无意义的内容。我在自己的机器上进行了一个快速测试,得到了-24的结果,但不保证你能复现此结果。

问题出在允许从Stringchar *的隐式转换上。当我们尝试相减两个String对象时,编译器试图弄清楚我们的意图。由于它不能直接相减它们,它检查是否可以将它们转换为支持减法的某种类型——果然,char const *支持减法,因此它将我们的两个String对象转换为char const *,然后相减。

如果我们将该转换标记为explicit

explicit operator char const *() { return data.c_str(); }

如果试图减去两个String对象的代码将无法编译。

相同的基本思想也适用于explicit构造函数,但是为了演示它,我们通常需要至少涉及一对不同的类,因此代码要变得更长。


我不确定你的疑问转换为整数是否正确。如果我将一个double值传递给String对象,它将直接进入构造函数,该构造函数期望double作为参数(如果有的话),而不考虑显式声明。 - SandBag_1996
@UnderDog:是的和不是。是的,那就是重载决议会选择的函数。但是,如果它被标记为显式,并且你试图使用它来进行隐式转换,那么代码将无法编译。 - Jerry Coffin
我理解并同意这一点。但我的问题不是关于那个的。我更想知道在哪些情况下或者有没有示例代码,如果不使用explicit会导致失败或者不良行为,因为到目前为止,根据我所理解的解释,我认为使用explicit没有太大的价值。 - SandBag_1996
@UnderDog:我已经添加了一个例子。 - Jerry Coffin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接