构造函数使用 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))
...因为这涉及从 double
到 int
的隐式转换,然后是从 int
到 String
的显式转换。如果你想禁止从 double
到 String
的转换,你可以通过(例如)提供一个重载的构造函数来实现,该构造函数接受 double
,然后抛出:
String(double) { throw("Conversion from double not allowed"); }
现在不会发生从 double
到 int
的隐式转换——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
,但不要指望复制该特定结果。
这里的问题源于允许从 String
到 char *
的隐式转换。当我们尝试减去两个 String
对象时,编译器会试图弄清楚我们的意思。由于它不能直接减去它们,它会查看是否可以将它们转换为某种支持减法的类型——果然,char const *
支持减法,所以它将我们的两个 String
对象都转换为 char const *
,然后减去两个指针。
如果我们将该转化标记为 explicit
:
explicit operator char const *() { return data.c_str(); }
...尝试减去两个 String
对象的代码根本无法编译。
相同的基本思想 can/does 适用于 explicit
构造函数,但演示它的代码变得更长,因为我们通常至少需要涉及几个不同的 classes。
我试图了解 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))
...因为这涉及从 double
到 int
的隐式转换,然后是从 int
到 String
的显式转换。如果你想禁止从 double
到 String
的转换,你可以通过(例如)提供一个重载的构造函数来实现,该构造函数接受 double
,然后抛出:
String(double) { throw("Conversion from double not allowed"); }
现在不会发生从 double
到 int
的隐式转换——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
,但不要指望复制该特定结果。
这里的问题源于允许从 String
到 char *
的隐式转换。当我们尝试减去两个 String
对象时,编译器会试图弄清楚我们的意思。由于它不能直接减去它们,它会查看是否可以将它们转换为某种支持减法的类型——果然,char const *
支持减法,所以它将我们的两个 String
对象都转换为 char const *
,然后减去两个指针。
如果我们将该转化标记为 explicit
:
explicit operator char const *() { return data.c_str(); }
...尝试减去两个 String
对象的代码根本无法编译。
相同的基本思想 can/does 适用于 explicit
构造函数,但演示它的代码变得更长,因为我们通常至少需要涉及几个不同的 classes。