explicit
C++中的关键字是什么意思?
10 回答
允许编译器进行一次隐式转换以将参数解析为函数。这意味着编译器可以使用可通过单个参数调用的构造函数从一种类型转换为另一种类型,以便为参数获取正确的类型。
这是一个带有构造函数的示例类,可用于隐式转换:
class Foo
{
private:
int m_foo;
public:
// single parameter constructor, can be used as an implicit conversion
Foo (int foo) : m_foo (foo) {}
int GetFoo () { return 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
带有构造函数的类,该构造函数构造给定大小的字符串。您有一个函数print(const MyString&)
(以及一个重载print (char *string)
),并且您调用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++ 中,只有一个必需参数的构造函数被视为隐式转换函数。它将参数类型转换为类类型。这是否是一件好事取决于构造函数的语义。
例如,如果您有一个带有 constructor 的字符串类String(const char* s)
,那可能正是您想要的。您可以将 a 传递const char*
给期望 a 的函数String
,编译器会自动String
为您构造一个临时对象。
另一方面,如果您有一个缓冲区类,其构造函数Buffer(int size)
以字节为单位获取缓冲区的大小,您可能不希望编译器悄悄地将int
s 转换为Buffer
s。为了防止这种情况,您使用explicit
关键字声明构造函数:
class Buffer { explicit Buffer(int size); ... }
那样,
void useBuffer(Buffer& buf);
useBuffer(4);
成为编译时错误。如果你想传递一个临时Buffer
对象,你必须明确地这样做:
useBuffer(Buffer(4));
总之,如果您的单参数构造函数将参数转换为您的类的对象,您可能不想使用explicit
关键字。但是如果你有一个构造函数只是碰巧接受一个参数,你应该声明它explicit
以防止编译器意外转换让你感到惊讶。
关键字explicit
伴随着
- 类 X 的构造函数,不能用于将第一个(任何唯一)参数隐式转换为 X 类型
C++ [class.conv.ctor]
1) 没有函数说明符显式声明的构造函数指定从其参数类型到其类的类型的转换。这样的构造函数称为转换构造函数。
2) 显式构造函数像非显式构造函数一样构造对象,但仅在显式使用直接初始化语法 (8.5) 或强制类型转换 (5.2.9, 5.4) 的情况下这样做。默认构造函数可以是显式构造函数;这样的构造函数将用于执行默认初始化或值初始化(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
隐式可构造 fromV
和一个函数分别为和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'; }
如果向量的大小构造函数不明确,则可以像这样调用函数:
print_intvector(3);
人们对这样的电话有何期待?一行包含3
或三行包含0
?(第二个是发生了什么。)
在类接口中使用显式关键字会强制接口的用户明确说明所需的转换。
正如 Bjarne Stroustrup 所说(在“The C++ Programming Language”,第 4 版,35.2.1,第 1011 页)关于为什么std::duration
不能从普通数字隐式构造的问题:
如果您知道自己的意思,请明确说明。
这个答案是关于有/没有显式构造函数的对象创建,因为它没有包含在其他答案中。
考虑以下没有显式构造函数的类:
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
用于强制显式调用构造函数。
class C {
public:
explicit C() =default;
};
int main() {
C c;
return 0;
}
explicit
构造函数前面的-keywordC()
告诉编译器只允许显式调用此构造函数。
explicit
-keyword 也可用于用户定义的类型转换运算符:
class C{
public:
explicit inline operator bool() const {
return true;
}
};
int main() {
C c;
bool b = static_cast<bool>(c);
return 0;
}
在这里,explicit
-keyword 仅强制显式强制转换有效,因此bool b = c;
在这种情况下将是无效强制转换。在这种情况下,explicit
-keyword 可以帮助程序员避免隐式的、无意的强制转换。这种用法已在C++11中标准化。
Cpp 参考总是有帮助的!!!可以在此处找到有关显式说明符的详细信息。您可能还需要查看隐式转换和复制初始化。
快速浏览
显式说明符指定构造函数或转换函数(C++11 起)不允许隐式转换或复制初始化。
示例如下:
struct A
{
A(int) { } // converting constructor
A(int, int) { } // converting constructor (C++11)
operator bool() const { return true; }
};
struct B
{
explicit B(int) { }
explicit B(int, int) { }
explicit operator bool() const { return true; }
};
int main()
{
A a1 = 1; // OK: copy-initialization selects A::A(int)
A a2(2); // OK: direct-initialization selects A::A(int)
A a3 {4, 5}; // OK: direct-list-initialization selects A::A(int, int)
A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
A a5 = (A)1; // OK: explicit cast performs static_cast
if (a1) cout << "true" << endl; // OK: A::operator bool()
bool na1 = a1; // OK: copy-initialization selects A::operator bool()
bool na2 = static_cast<bool>(a1); // OK: static_cast performs direct-initialization
// B b1 = 1; // error: copy-initialization does not consider B::B(int)
B b2(2); // OK: direct-initialization selects B::B(int)
B b3 {4, 5}; // OK: direct-list-initialization selects B::B(int, int)
// B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
B b5 = (B)1; // OK: explicit cast performs static_cast
if (b5) cout << "true" << endl; // OK: B::operator bool()
// bool nb1 = b2; // error: copy-initialization does not consider B::operator bool()
bool nb2 = static_cast<bool>(b2); // OK: static_cast performs direct-initialization
}
如前所述,使您的一个参数构造函数(包括具有默认值的构造函数)始终是一种很好的编码arg2
实践arg3
。像往常一样使用 C++:如果你不这样做 - 你会希望你这样做...
另一个类的好习惯是让复制构造和赋值私有(也就是禁用它),除非你真的需要实现它。这样可以避免在使用 C++ 默认为您创建的方法时最终获得指针副本。另一种方法是从boost::noncopyable
.
构造函数附加隐式转换。为了抑制这种隐式转换,需要使用显式参数声明构造函数。
在 C++11 中,您还可以使用这样的关键字http://en.cppreference.com/w/cpp/language/explicit指定“运算符类型()”使用这样的规范,您可以在显式转换方面使用运算符,并且直接初始化对象。
PS 当使用由用户定义的转换(通过构造函数和类型转换运算符)时,只允许使用一级隐式转换。但是您可以将此转换与其他语言转换结合使用
- 提升积分等级(char 到 int,float 到 double);
- 标准转换(int 到 double);
- 将对象的指针转换为基类和 void*;