构造函数显式关键字的使用

usage of explicit keyword for constructors

本文关键字:关键字 构造函数      更新时间:2023-10-16

我试图了解显式关键字在 c++ 中的用法,并在 SO 上查看了这个问题显式关键字在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;

它将始终默认为带有双精度作为参数的构造函数。所以,我很难理解显式的真正用法。你能给我一个例子,不使用显式将是一个真正的失败吗?

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

至于使用 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构造函数,但是演示它的代码会变得更长,因为我们通常需要至少涉及几个不同的类。