什么是 explicit
关键字在C ++中的意思?
什么是 explicit
关键字在C ++中的意思?
允许编译器进行一次隐式转换以将参数解析为函数。这意味着编译器可以使用可调用的构造函数 单个参数 从一种类型转换为另一种类型以获得参数的正确类型。
这是一个带有构造函数的示例类,可用于隐式转换:
class Foo
{
public:
// single parameter constructor, can be used as an implicit conversion
Foo (int foo) : m_foo (foo)
{
}
int GetFoo () { return m_foo; }
private:
int m_foo;
};
这是一个简单的函数,需要一个 Foo
目的:
void DoBar (Foo foo)
{
int i = foo.GetFoo ();
}
而这里就是 DoBar
函数被调用。
int main ()
{
DoBar (42);
}
这个论点不是 Foo
对象,但是 int
。但是,存在一个构造函数 Foo
需要一个 int
所以这个构造函数可以用来将参数转换为正确的类型。
允许编译器为每个参数执行一次此操作。
前缀 explicit
构造函数的关键字可防止编译器将该构造函数用于隐式转换。将它添加到上面的类将在函数调用时创建编译器错误 DoBar (42)
。现在有必要明确地调用转换 DoBar (Foo (42))
您可能希望这样做的原因是为了避免可以隐藏错误的意外构造。举例:
MyString(int size)
带有构造函数的类,该构造函数构造给定大小的字符串。你有一个功能 print(const MyString&)
,你打电话 print(3)
(当你 其实 打算打电话 print("3")
)。你希望它打印“3”,但它打印一个长度为3的空字符串。假设你有一个班级 String
:
class String {
public:
String(int n); // allocate n bytes to the String object
String(const char *p); // initializes object with char *p
};
现在,如果你尝试:
String mystring = 'x';
人物 'x'
将被隐式转换为 int
然后是 String(int)
构造函数将被调用。但是,这不是用户可能想要的。因此,为了防止这种情况,我们将构造函数定义为 explicit
:
class String {
public:
explicit String (int n); //allocate n bytes
String(const char *p); // initialize sobject with string p
};
在C ++中,只有一个必需参数的构造函数被认为是隐式转换函数。它将参数类型转换为类类型。这是否是好事取决于构造函数的语义。
例如,如果您有一个带构造函数的字符串类 String(const char* s)
,这可能正是你想要的。你可以传递一个 const char*
期待一个功能 String
,编译器会自动构造一个临时的 String
对象为你。
另一方面,如果你有一个缓冲类,其构造函数 Buffer(int size)
以字节为单位获取缓冲区的大小,您可能不希望编译器悄然转向 int
进入 Buffer
秒。为了防止这种情况,你用构造函数声明构造函数 explicit
关键词:
class Buffer { explicit Buffer(int size); ... }
那样,
void useBuffer(Buffer& buf);
useBuffer(4);
成为编译时错误。如果你想传递一个临时的 Buffer
对象,你必须明确地这样做:
useBuffer(Buffer(4));
总之,如果您的单参数构造函数将参数转换为类的对象,您可能不希望使用 explicit
关键词。但是如果你有一个构造函数只是碰巧采用一个参数,你应该声明它 explicit
防止编译器意外转换让您感到惊讶。
这个答案是关于有/没有显式构造函数的对象创建,因为它没有在其他答案中涵盖。
考虑以下没有显式构造函数的类:
class Foo
{
public:
Foo(int x) : m_x(x)
{
}
private:
int m_x;
};
类Foo的对象可以通过两种方式创建:
Foo bar1(10);
Foo bar2 = 20;
根据实现,实例化类Foo的第二种方式可能令人困惑,或者不是程序员想要的。前缀 explicit
构造函数的关键字会在生成编译器错误 Foo bar2 = 20;
。
它是 平时 将单参数构造函数声明为的好方法 explicit
,除非您的实施明确禁止它。
还要注意构造函数
都可以用作单参数构造函数。所以你可能也想做这些 explicit
。
你故意的一个例子 不 想要使你的单参数构造函数显式是你正在创建一个仿函数(看看'add_x'结构声明在 这个 回答)。在这种情况下,将对象创建为 add_x add30 = 30;
可能会有意义。
这里 对显式构造函数是一个很好的写作。
该 explicit
keyword使转换构造函数成为非转换构造函数。因此,代码不易出错。
explicit
伴随着C ++ [class.conv.ctor]
1)在没有函数说明符explicit的情况下声明的构造函数指定从其参数类型到其类类型的转换。这样的构造函数称为转换构造函数。
2)显式构造函数与非显式构造函数一样构造对象,但仅在显式使用直接初始化语法(8.5)或强制转换(5.2.9,5.4)的情况下才这样做。默认构造函数可以是显式构造函数;这样的构造函数将用于执行默认初始化或valueinitialization (8.5)。
C ++ [class.conv.fct]
2)转换函数可以是显式的(7.1.2),在这种情况下,它仅被视为直接初始化(8.5)的用户定义转换。否则,用户定义的转换不限于在分配中使用 和初始化。
显式转换函数和构造函数只能用于显式转换(直接初始化或显式转换操作),而非显式构造函数和转换函数可用于隐式转换和显式转换。
/*
explicit conversion implicit conversion
explicit constructor yes no
constructor yes yes
explicit conversion function yes no
conversion function yes yes
*/
X, Y, Z
和功能 foo, bar, baz
:让我们看一下结构和函数的一小部分,看看它们之间的区别 explicit
和非explicit
转换。
struct Z { };
struct X {
explicit X(int a); // X can be constructed from int explicitly
explicit operator Z (); // X can be converted to Z explicitly
};
struct Y{
Y(int a); // int can be implicitly converted to Y
operator Z (); // Y can be implicitly converted to Z
};
void foo(X x) { }
void bar(Y y) { }
void baz(Z z) { }
转换函数参数:
foo(2); // error: no implicit conversion int to X possible
foo(X(2)); // OK: direct initialization: explicit conversion
foo(static_cast<X>(2)); // OK: explicit conversion
bar(2); // OK: implicit conversion via Y(int)
bar(Y(2)); // OK: direct initialization
bar(static_cast<Y>(2)); // OK: explicit conversion
对象初始化:
X x2 = 2; // error: no implicit conversion int to X possible
X x3(2); // OK: direct initialization
X x4 = X(2); // OK: direct initialization
X x5 = static_cast<X>(2); // OK: explicit conversion
Y y2 = 2; // OK: implicit conversion via Y(int)
Y y3(2); // OK: direct initialization
Y y4 = Y(2); // OK: direct initialization
Y y5 = static_cast<Y>(2); // OK: explicit conversion
X x1{ 0 };
Y y1{ 0 };
转换函数参数:
baz(x1); // error: X not implicitly convertible to Z
baz(Z(x1)); // OK: explicit initialization
baz(static_cast<Z>(x1)); // OK: explicit conversion
baz(y1); // OK: implicit conversion via Y::operator Z()
baz(Z(y1)); // OK: direct initialization
baz(static_cast<Z>(y1)); // OK: explicit conversion
对象初始化:
Z z1 = x1; // error: X not implicitly convertible to Z
Z z2(x1); // OK: explicit initialization
Z z3 = Z(x1); // OK: explicit initialization
Z z4 = static_cast<Z>(x1); // OK: explicit conversion
Z z1 = y1; // OK: implicit conversion via Y::operator Z()
Z z2(y1); // OK: direct initialization
Z z3 = Z(y1); // OK: direct initialization
Z z4 = static_cast<Z>(y1); // OK: explicit conversion
explicit
转换函数或构造函数?转换构造函数和非显式转换函数可能引入歧义。
考虑一个结构 V
,可转换为 int
,一个结构 U
隐含地可构建 V
和一个功能 f
超载了 U
和 bool
分别。
struct V {
operator bool() const { return true; }
};
struct U { U(V) { } };
void f(U) { }
void f(bool) { }
打电话给 f
如果传递一个类型的对象是不明确的 V
。
V x;
f(x); // error: call of overloaded 'f(V&)' is ambiguous
编译器不知道使用构造函数 U
或转换函数转换 V
将对象转换为传递给的类型 f
。
如果是构造函数的 U
或者转换函数 V
将会 explicit
,没有歧义,因为只考虑非显式转换。如果两者都是明确的呼吁 f
使用类型的对象 V
必须使用显式转换或强制转换操作来完成。
转换构造函数和非显式转换函数可能会导致意外行为。
考虑打印一些向量的函数:
void print_intvector(std::vector<int> const &v) { for (int x : v) std::cout << x << '\n'; }
如果向量的size-constructor不是显式的,则可以像这样调用函数:
print_intvector(3);
人们对这样的电话会有什么期望?一行包含 3
或三行包含 0
? (第二个是发生的事情。)
正如Bjarne Stroustrup所说(关于“C ++编程语言”,第4版,35.2.1,第1011页) std::duration
不能用普通数字隐式构造:
如果你知道你的意思,请明确说明。
该 explicit
-keyword可用于强制执行要调用的构造函数 明确地。
class C{
public:
explicit C(void) = default;
};
int main(void){
C c();
return 0;
}
该 explicit
构造函数前面的-keyword C(void)
告诉编译器只允许显式调用此构造函数。
该 explicit
-keyword也可以在用户定义的类型转换运算符中使用:
class C{
public:
explicit inline operator bool(void) const{
return true;
}
};
int main(void){
C c;
bool b = static_cast<bool>(c);
return 0;
}
这里, explicit
-keyword只强制显式转换为有效,所以 bool b = c;
在这种情况下,将是无效的演员。在这样的情况下 explicit
-keyword可以帮助程序员避免隐式的,非预期的强制转换。这种用法已经标准化了 C ++ 11。