关于C++:显式关键字是什么意思?

What does the explicit keyword mean?

在C++中EDCOX1的0个关键字是什么意思?


编译器可以进行一次隐式转换以将参数解析为函数。这意味着编译器可以使用带有单个参数的可调用构造函数从一种类型转换为另一种类型,以便为参数获得正确的类型。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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对象:

1
2
3
4
void DoBar (Foo foo)
{
  int i = foo.GetFoo ();
}

这里是调用DoBar函数的地方。

1
2
3
4
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

1
2
3
4
5
class String {
public:
    String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};

现在,如果您尝试:

1
String mystring = 'x';

字符'x'将隐式转换为int,然后调用String(int)构造函数。但是,这并不是用户想要的。因此,为防止出现这种情况,我们将把施工单位定义为explicit

1
2
3
4
5
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类,其构造函数Buffer(int size)以字节表示缓冲区的大小,则可能不希望编译器悄悄地将int转换为Buffers。为了防止这种情况发生,请使用explicit关键字声明该构造函数:

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

那样,

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

变成编译时错误。如果要传递临时Buffer对象,必须明确:

1
useBuffer(Buffer(4));

总之,如果单个参数构造函数将参数转换为类的对象,则可能不想使用explicit关键字。但是,如果有一个构造函数恰好接受一个参数,那么您应该将它声明为explicit,以防止编译器意外地对您进行转换。


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

考虑不带显式构造函数的以下类:

1
2
3
4
5
6
7
8
9
10
class Foo
{
public:
    Foo(int x) : m_x(x)
    {
    }

private:
    int m_x;
};

可以通过两种方式创建foo类的对象:

1
2
3
Foo bar1(10);

Foo bar2 = 20;

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

通常将单参数构造函数声明为explicit是一种良好的实践,除非您的实现特别禁止这样做。

还要注意的是,

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

都可以用作单参数构造函数。所以你可能想把这些也做成explicit

如果您有意不想让单参数构造函数显式化,例如,如果您正在创建一个函数(请查看此答案中声明的"add_x"结构)。在这种情况下,创建像add_x add30 = 30;这样的对象可能是有意义的。

下面是一篇关于显式构造函数的好文章。


explicit关键字使转换构造函数成为非转换构造函数。因此,代码不太容易出错。


关键字explicit

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

C++ [class.conv.ctor]

1) A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters to the type of its class. Such a constructor is called a converting constructor.

2) An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used. A default constructor may be an explicit constructor; such a constructor will be used to perform default-initialization or valueinitialization
(8.5).

  • 或仅用于直接初始化和显式转换的转换函数。

C++ [class.conv.fct]

2) A conversion function may be explicit (7.1.2), in which case it is only considered as a user-defined conversion for direct-initialization (8.5). Otherwise, user-defined conversions are not restricted to use in assignments
and initializations.

概述

显式转换函数和构造函数只能用于显式转换(直接初始化或显式转换操作),而非显式构造函数和转换函数可以用于隐式和显式转换。

1
2
3
4
5
6
7
8
9
10
11
12
/*
                                 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转换之间的区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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) { }

有关构造函数的示例:

函数参数的转换:

1
2
3
4
5
6
7
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

对象初始化:

1
2
3
4
5
6
7
8
9
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

有关转换函数的示例:

1
2
X x1{ 0 };
Y y1{ 0 };

函数参数的转换:

1
2
3
4
5
6
7
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

对象初始化:

1
2
3
4
5
6
7
8
9
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转换函数或构造函数?

转换构造函数和非显式转换函数可能会引入歧义。

分别考虑可转换为int的结构V、可从V隐式构造的结构U和为Ubool过载的函数f

1
2
3
4
5
6
7
8
struct V {
  operator bool() const { return true; }
};

struct U { U(V) { } };

void f(U) { }
void f(bool) {  }

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

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

编译器不知道是否使用U的构造函数或转换函数将V对象转换为类型以传递给f

如果EDOCX1的构造器(8)或EDOCX1的转换函数(6)为explicit,则不会有歧义,因为只考虑非显式转换。如果两者都是显式的,则使用V类型的对象调用f必须使用显式转换或强制转换操作完成。

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

考虑一个函数打印一些向量:

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

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

1
print_intvector(3);

这样的电话会有什么好处?一行包含3,还是三行包含0?(第二个是发生的情况。)

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

正如Bjarne Stroustrup所说的(在C++编程语言中,第四ED.,35.2.1,第1011页)关于为什么EDCOX1〔25〕不能从一个素数隐式构造的问题:

If you know what you mean, be explicit about it.


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

1
2
3
4
5
6
7
8
9
class C{
public:
    explicit C(void) = default;
};

int main(void){
    C c();
    return 0;
}

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

explicit关键字也可用于用户定义的类型转换运算符:

1
2
3
4
5
6
7
8
9
10
11
12
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中已经被标准化了。


显式转换构造函数(C++)

The explicit function specifier controls unwanted implicit type
conversions. It can only be used in declarations of constructors
within a class declaration. For example, except for the default
constructor, the constructors in the following class are conversion
constructors.

1
2
3
4
5
6
7
class A
{
public:
    A();
    A(int);
    A(const char*, int = 0);
};

下列声明是合法的:

1
2
A c = 1;
A d ="Venditti";

第一个声明相当于A c = A( 1 );

如果将类的构造函数声明为explicit,则前面的声明将是非法的。

例如,如果将类声明为:

1
2
3
4
5
6
7
class A
{
public:
    explicit A();
    explicit A(int);
    explicit A(const char*, int = 0);
};

只能指定与类类型的值匹配的值。

例如,以下陈述是合法的:

1
2
3
4
5
6
7
  A a1;
  A a2 = A(1);
  A a3(1);
  A a4 = A("Venditti");
  A* p = new A(1);
  A a5 = (A)1;
  A a6 = static_cast<A>(1);


CPP参考总是有用的!!!!有关显式说明符的详细信息,请参见此处。您可能还需要查看隐式转换和复制初始化。

快看

The explicit specifier specifies that a constructor or conversion function (since C++11) doesn't allow implicit conversions or copy-initialization.

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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派生。


构造函数追加隐式转换。要禁止此隐式转换,需要使用参数explicit声明构造函数。

在C++ 11中,还可以指定一个"操作符Type()),用这样的关键字HTTP://E.CPAPYCENCE.COM/W/CPP/语言/显式,使用这种规范,可以使用显式转换的操作符,以及对象的直接初始化。

p.s.当使用由用户定义的转换(通过构造函数和类型转换运算符)时,只允许使用一个级别的隐式转换。但是您可以将这种转换与其他语言转换结合起来

  • 向上整数级(char到int,float到double);
  • 标准转换(int到double);
  • 将对象指针转换为基类和void*;