显式的关键字是什么意思?

有人在C ++中发布了关于explicit关键字含义的另一个问题的评论。 那么,这是什么意思?

允许编译器进行一次隐式转换,将参数parsing为一个函数。 这意味着编译器可以使用可用一个参数调用的构造函数将一种types转换为另一种types,以便为参数获取正确的types。

下面是一个带有可用于隐式转换的构造函数的示例类:

 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因此可以使用此构造函数将参数转换为正确的types。

编译器允许为每个参数执行一次。

explicit关键字添加到构造函数的前缀可防止编译器使用该构造函数进行隐式转换。 将它添加到上面的类将在函数调用DoBar (42)创build一个编译器错误。 现在有必要呼吁明确转换DoBar (Foo (42))

您可能想要这样做的原因是为了避免意外的构build,可以隐藏错误。 被举的例子:

  • 你有一个MyString(int size)类,它有一个构造函数来构造给定大小的string。 你有一个函数print(const MyString&) ,并且调用print(3) (当你打算print("3") )。 您希望它打印“3”,但打印一个长度为3的空string。

假设你有一个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'; 

char'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 ++中,只有一个必需参数的构造函数被认为是一个隐式转换函数。 它将参数types转换为类types。 这是否是件好事取决于构造函数的语义。

例如,如果你有一个带有构造函数String(const char* s)的string类,那可能正是你想要的。 你可以传递一个const char*给函数,期望一个String ,编译器会自动为你构造一个临时的String对象。

另一方面,如果你有一个缓冲类,它的构造函数Buffer(int size)以字节为单位取缓冲区的大小,你可能不希望编译器把int变成Buffer s。 为了防止这种情况,您使用explicit关键字声明构造函数:

 class Buffer { explicit Buffer(int size); ... } 

那样,

 void useBuffer(Buffer& buf); useBuffer(4); 

成为编译时错误。 如果你想传递一个临时的Buffer对象,你必须明确地这样做:

 useBuffer(Buffer(4)); 

总之,如果你的单参数构造函数将参数转换成你的类的一个对象,你可能不想使用explicit关键字。 但是如果你有一个构造函数只是简单地使用一个参数,你应该声明它是explicit以防止编译器意外地转换你的意外。

explicit关键字将转换构造函数转换为非转换构造函数。 结果,代码不太容易出错。

这个答案是关于使用/不使用显式构造函数的对象创build,因为在其他答案中没有涉及。

考虑下面的类没有显式的构造函数:

 class Foo { public: Foo(int x) : m_x(x) { } private: int m_x; }; 

Foo类的对象可以用两种方式创build:

 Foo bar1(10); Foo bar2 = 20; 

根据实现,实例化Foo类的第二种方式可能会令人困惑,或者不是程序员所期望的。 将explicit关键字前缀到构造函数会在Foo bar2 = 20;处生成编译器错误Foo bar2 = 20;

除非您的实现明确禁止,否则通常将单参数构造函数声明为explicit

还要注意构造函数

  • 所有参数的默认参数,或
  • 第二个参数的默认参数

都可以用作单参数构造函数。 所以你可能想要使这些也是explicit

一个例子,当你故意不想让你的单参数构造函数显式是如果你正在创build一个函子(看看这个答案中声明的“add_x”结构)。 在这种情况下,创build一个对象为add_x add30 = 30; 可能是有道理的。

这里是一个很好的关于显式构造函数的说明。

explicit关键字也伴随着

  • 一个类X的构造函数,不能用于将第一个(任何唯一的)参数隐式转换为Xtypes

C ++ [class.conv.ctor]

1)没有显式声明的构造函数指定了从其参数types到其类的types的转换。 这样的构造函数被称为转换构造函数。

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转换和非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 

有关转换function的示例:

 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 ,一个可以从V隐式构造的结构体U和一个分别为Ubool重载的函数f

 struct V { operator bool() const { return true; } }; struct U { U(V) { } }; void f(U) { } void f(bool) { } 

如果传递types为V的对象,则对f的调用是不明确的。

 V x; f(x); // error: call of overloaded 'f(V&)' is ambiguous 

编译器不知道使用U的构造函数还是转换函数将V对象转换为传递给f的types。

如果U的构造函数或V的转换函数是explicit ,那么就不会有含糊的地方,因为只有非显式的转换才会被考虑。 如果两者都是显式的,则使用typesV的对象对f的调用将必须使用显式的转换或转换操作来完成。

转换构造函数和非显式转换函数可能会导致意外的行为。

考虑打印一些vector的函数:

 void print_intvector(std::vector<int> const &v) { for (int x : v) std::cout << x << '\n'; } 

如果向量的大小构造函数不是显式的,那么可以像这样调用函数:

 print_intvector(3); 

这个电话会给人什么期望? 一行包含3或3行包含0 ? (第二个是发生什么的地方)

在类接口中使用显式关键字可强制接口的用户对所需的转换进行明确的说明。

正如Bjarne Stroustrup在“C ++编程语言”第4版35.2.1第1011页中所提到的那样,为什么std::duration不能用一个普通的数字隐含地构造:

如果你知道你的意思,就明确表示一下。

explicit关键字可用于强制显式调用构造函数

 class C{ public: explicit C(void) = default; }; int main(void){ C c(); return 0; } 

构造函数C(void)前面的explicit关键字告诉编译器只允许对此构造函数的显式调用。

explicit关键字也可以在用户定义的types转换运算符中使用:

 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关键字只强制显式强制转换,所以bool b = c; 在这种情况下将是无效的投。 在这些explicit关键字的情况下,可以帮助程序员避免隐式的,无意的强制转换。 这个用法已经在C ++ 11中被标准化了。

这已经被讨论过了( 什么是显式构造函数 )。 但是我必须说,这里没有详细的描述。

另外,如前所述,构造一个参数构造函数(包括那些具有arg2,arg3,…的默认值的构造函数)总是一个很好的编码习惯。 像C ++一样:如果你不这样做 – 你会希望你做到的

除非你真的需要实现它,否则另一个好的做法是将拷贝构造和赋值私有化(也就是禁用它)。 这样可以避免在使用C ++为您默认创build的方法时最终获得指针副本。 另一种方法是从boost :: noncopyable派生。

Cpp参考是总是有帮助的! 关于显式说明符的细节可以在这里find。 您可能还需要查看隐式转换和复制初始化 。

快速查看

显式说明符指定构造函数或转换函数(自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 } 

构造函数追加隐式转换。 为了抑制这种隐式转换,需要声明一个带有显式参数的构造函数。

在C ++ 11中,还可以使用关键字http://en.cppreference.com/w/cpp/language/explicit来指定“运算符types()”使用此类规范,可以使用运算符来显式转换,直接初始化对象。;

PS当使用由USER定义的转换(通过构造函数和types转换运算符)时,只允许使用一个隐式转换级别。 但是,您可以将此转化与其他语言转换结合起来

  • 积分等级(char为int,float为double);
  • 标准转换(int为double);
  • 将对象的指针转换为基类,并void *;