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

San*_*996 10 c++ constructor explicit class

我试图理解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;
}
Run Code Online (Sandbox Code Playgroud)

现在我已经将String构造函数声明为显式,但是如果我不将它列为显式,如果我将构造函数称为,

String mystring('x');
Run Code Online (Sandbox Code Playgroud)

要么

String mystring = 'x';
Run Code Online (Sandbox Code Playgroud)

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

String mystring(2.5);
Run Code Online (Sandbox Code Playgroud)

或者这样

String mystring = 2.5;
Run Code Online (Sandbox Code Playgroud)

它总是默认为带有double参数的构造函数.所以,我很难理解显式的真实用法.你能给我一个例子吗?不使用显式将是一个真正的失败?

Jer*_*fin 7

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
};
Run Code Online (Sandbox Code Playgroud)

然后让我们定义一个接收类型参数的函数String(也可以String const &,但String目前会这样做):

int f(String);
Run Code Online (Sandbox Code Playgroud)

您的构造函数允许隐式转换char const *,但只允许显式转换int.这意味着如果我打电话:

f("this is a string");
Run Code Online (Sandbox Code Playgroud)

...编译器将生成代码以从字符串文字构造String对象,然后f使用该String对象调用.

但是,如果您尝试致电:

f(2);
Run Code Online (Sandbox Code Playgroud)

它将失败,因为已经标记String了带int参数的构造函数explicit.这意味着如果我想将inta 转换为a String,我必须明确地做到:

f(String(2));
Run Code Online (Sandbox Code Playgroud)

如果String(char const *);构造函数也被标记explicit,那么你也无法调用f("this is a string")- 你必须使用f(String("this is a string"));

但请注意,explicit仅控制从某种类型foo到您定义的类型的隐式转换.它从一些其他类型的隐式转换没有影响你的类型explicit的构造需要.因此,采用类型的显式构造函数int仍将采用浮点参数:

f(String(1.2))
Run Code Online (Sandbox Code Playgroud)

...因为这涉及从隐式转换doubleint后跟显式转换intString.如果你想禁止转换doubleString,你可以通过(例如)提供一个重载的构造函数来获取double,然后抛出:

String(double) { throw("Conversion from double not allowed"); }
Run Code Online (Sandbox Code Playgroud)

现在从隐式转换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(); }
};
Run Code Online (Sandbox Code Playgroud)

(我通过使用std::string来存储数据而作弊,但如果我喜欢它们并且存储了一个char *并且用于new分配内存,那么情况也是如此).

现在,这使得这样的工作正常:

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

std::cout << a + b;
Run Code Online (Sandbox Code Playgroud)

......(当然)结果是打印出来ab.但是,如果用户犯了一个小错误并-输入他们打算输入的内容,会发生什么+

这就是事情变得丑陋的地方 - 代码仍在编译和"工作"(对于单词的某些定义),但打印出废话.在我的机器上进行快速测试,我得到了-24,但不要指望重复那个特定的结果.

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

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

explicit operator char const *() { return data.c_str(); }
Run Code Online (Sandbox Code Playgroud)

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

同样的基本思想可以/确实适用于explicit构造函数,但是演示它的代码变得更长,因为我们通常至少需要涉及几个不同的类.