明确的关键字是什么意思?

explicit关键字在C++中是什么意思?

1108983 次浏览

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

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

class Foo{private:int m_foo;
public:// single parameter constructor, can be used as an implicit conversionFoo (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 objectString(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 bytesString(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,以防止编译器以意外的转换让你感到惊讶。

将您的单参数构造函数(包括默认值为arg2arg3、…的构造函数)设置为已经声明的构造函数始终是一个很好的编码实践。像往常一样C++:如果你不-你会希望你做了…

类的另一个好做法是将复制构造和赋值设为私有(也就是禁用它),除非你真的需要实现它。这避免了在使用默认情况下C++将为你创建的方法时拥有指针的最终副本。另一种方法是从boost::noncopyable派生的。

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

explicit-关键字可用于强制调用明确的构造函数。

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

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

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

class C{public:explicit inline operator bool() const {return true;}};
int main() {C c;bool b = static_cast<bool>(c);return 0;}

在这里,explicit-关键字仅强制显式强制转换有效,因此bool b = c;在这种情况下将是无效的强制转换。在这种情况下,explicit-关键字可以帮助程序员避免隐式的、意外的强制转换。这种用法已在C++11中标准化。

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

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

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;可能是有意义的。

这里是关于显式构造函数的一篇很好的文章。

构造函数附加隐式转换。要抑制此隐式转换,需要声明一个带有显式参数的构造函数。

在C++11中,您还可以使用关键字http://en.cppreference.com/w/cpp/language/explicit指定“运算符类型()”。通过这样的规范,您可以使用运算符进行显式转换,并直接初始化对象。

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

  • 向上积分排名(char到int,浮点数到双精度);
  • 标准转换(int到Double);
  • 将对象指针转换为基类和空*;

关键字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 explicitlyexplicit operator Z (); // X can be converted to Z explicitly};
struct Y{Y(int a); // int can be implicitly converted to Yoperator 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 possiblefoo(X(2));                  // OK: direct initialization: explicit conversionfoo(static_cast<X>(2));     // OK: explicit conversion
bar(2);                     // OK: implicit conversion via Y(int)bar(Y(2));                  // OK: direct initializationbar(static_cast<Y>(2));     // OK: explicit conversion

对象初始化:

X x2 = 2;                   // error: no implicit conversion int to X possibleX x3(2);                    // OK: direct initializationX x4 = X(2);                // OK: direct initializationX x5 = static_cast<X>(2);   // OK: explicit conversion
Y y2 = 2;                   // OK: implicit conversion via Y(int)Y y3(2);                    // OK: direct initializationY y4 = Y(2);                // OK: direct initializationY y5 = static_cast<Y>(2);   // OK: explicit conversion

关于转换函数的示例:

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

函数参数的转换:

baz(x1);                    // error: X not implicitly convertible to Zbaz(Z(x1));                 // OK: explicit initializationbaz(static_cast<Z>(x1));    // OK: explicit conversion
baz(y1);                    // OK: implicit conversion via Y::operator Z()baz(Z(y1));                 // OK: direct initializationbaz(static_cast<Z>(y1));    // OK: explicit conversion

对象初始化:

Z z1 = x1;                  // error: X not implicitly convertible to ZZ z2(x1);                   // OK: explicit initializationZ z3 = Z(x1);               // OK: explicit initializationZ z4 = static_cast<Z>(x1);  // OK: explicit conversion
Z z1 = y1;                  // OK: implicit conversion via Y::operator Z()Z z2(y1);                   // OK: direct initializationZ z3 = Z(y1);               // OK: direct initializationZ z4 = static_cast<Z>(y1);  // OK: explicit conversion

为什么使用explicit转换函数或构造函数?

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

考虑一个结构V,可转换为int,一个结构U可从V隐式构造,一个函数f分别为Ubool重载。

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

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

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

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

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

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

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

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

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

print_intvector(3);

人们对这样的调用会有什么期望?一行包含3还是三行包含0?(第二个是发生的事情。)

在类接口中使用显式关键字强制接口的用户显式地说明所需的转换。

正如Bjarne Stroustrup所说(在“C++编程语言”,第4版,35.2.1,第1011页)关于为什么std::duration不能由普通数字隐式构造的问题:

如果你知道你的意思,明确地说出来。

cpp参考总是有帮助的!!!有关显式说明符的详细信息可以在这里中找到。您可能还需要查看隐式转换复制初始化

快速浏览

显式说明符指定构造函数或转换函数(自C++11)不允许隐式转换或复制初始化。

示例如下:

struct A{A(int) { }      // converting constructorA(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_castif (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_castif (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}

其他答案缺少一个重要因素,我将在这里提到。

与“删除”关键字一起,“显式”允许您控制编译器生成特殊成员函数的方式-默认构造函数,复制构造函数,复制赋值操作符,析构函数,移动构造函数和移动赋值。

参考https://learn.microsoft.com/en-us/cpp/cpp/explicitly-defaulted-and-deleted-functions