构造函数使用 explicit 关键字

usage of explicit keyword for constructors

我试图了解 explicit 关键字在 c++ 中的用法,并在 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;
}

现在我已经将 String 构造函数声明为显式的,但是假设我没有将其列为显式的,如果我将构造函数调用为,

String mystring('x');

String mystring = 'x';

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

String mystring(2.5);

或者这样

String mystring = 2.5;

它将始终默认为以 double 作为参数的构造函数。所以,我很难理解显式的实际用法。你能给我举个例子,说明不使用 explicit 会是一个真正的失败吗?

explicit 旨在防止 隐式 转换。任何时候你使用像 String(foo); 这样的东西,这是一个显式转换,所以使用 explicit 不会改变它是否成功。

因此,让我们看一个确实涉及隐式转换的场景。让我们从你的 String class:

开始
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 将直接传递给您的 ctor 而无需转换。

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

用转​​换运算符比用构造函数更容易证明问题​​(因为只用一个 class 就可以做到)。例如,让我们考虑一个很小的字符串 class ,它与人们在了解隐式转换的问题性之前所写的很多内容非常相似:

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。但是如果用户犯了一个小错误并在他们想要输入 + 的地方输入了 - 会发生什么?

这就是事情变得丑陋的地方——代码仍然可以编译并且 "works"(对于这个词的某些定义),但是打印出一些废话。在我的机器上进行快速测试时,我得到了 -24,但不要指望复制该特定结果。

这里的问题源于允许从 Stringchar * 的隐式转换。当我们尝试减去两个 String 对象时,编译器会试图弄清楚我们的意思。由于它不能直接减去它们,它会查看是否可以将它们转换为某种支持减法的类型——果然,char const * 支持减法,所以它将我们的两个 String 对象都转换为 char const *,然后减去两个指针。

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

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

...尝试减去两个 String 对象的代码根本无法编译。

相同的基本思想 can/does 适用于 explicit 构造函数,但演示它的代码变得更长,因为我们通常至少需要涉及几个不同的 classes。