显式关键字是什么意思?

What does the explicit keyword mean?

提问人:Skizz 提问时间:9/23/2008 最后编辑:Ronny BrendelSkizz 更新时间:10/21/2023 访问量:1176047

问:

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

C C++-FAQ Explicit-构造函数

评论

225赞 chris 8/31/2012
我只想向任何新人指出,自 C++11 以来,它可以应用于不仅仅是构造函数。现在,当应用于转换运算符时,它也有效。假设您有一个类,无论出于何种原因,都有一个转换运算符 to 和一个显式转换运算符 to。你可以说 ,但你必须明确地(最好使用 )才能说 。explicitBigIntintstd::stringint i = myBigInt;static_caststd::string s = myBigInt;
0赞 curiousguy 6/20/2018
@chris 有一个显式关键字可用于隐式转换的声明。
0赞 Sz. 8/3/2019
@curiousguy:什么意思?所有转换都应该是隐式的吗?放开各种由于偶然的歧义而默默应用的滑稽错误投射?(例如,请参阅本C++参考页面的“安全布尔问题”部分,或 open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2333.html 了解为什么“显式转换是一个定义不明确的概念”是一个考虑不周的陈述的更多细节。
0赞 curiousguy 8/3/2019
@Sz。我的意思是,明确的皈依不是一回事;这是一个垃圾概念。此外,“安全布尔值”是“显式运算符”思想有用性的荒谬“证明”,因为它甚至不是该思想的应用,而是一组不同的规则,这意味着SL中“显式运算符”的唯一实际用途是临时的,不适用于UDT
2赞 chris 1/26/2021
@Milan,是的,就是这样。如果您正在寻找更多信息,此答案会更正式地写下来。请注意,bool 在这方面很特别。这些答案和搜索“显式转换运算符”将引导您找到更多有关此功能的文章,并且比评论链更适合。

答:

4146赞 24 revs, 19 users 45%Skizz #1

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

下面是一个转换构造函数的示例,显示了它是如何工作的:

struct Foo {
    // Single parameter constructor, can be used as an implicit conversion.
    // Such a constructor is called "converting constructor".
    Foo(int x) {}
};
struct Faz {
    // Also a converting constructor.
    Faz(Foo foo) {}
};

// The parameter is of type Foo, not of type int, so it looks like
// we have to pass a Foo.
void bar(Foo foo);

int main() {
    // However, the converting constructor allows us to pass an int.
    bar(42);
    // Also allowed thanks to the converting constructor.
    Foo foo = 42;
    // Error! This would require two conversions (int -> Foo -> Faz).
    Faz faz = 42;
}

在构造函数前面添加关键字前缀可防止编译器使用该构造函数进行隐式转换。将其添加到上述类中将在函数调用时创建编译器错误。现在有必要明确地调用转换explicitbar(42)bar(Foo(42))

您可能希望这样做的原因是为了避免可能隐藏错误的意外构造。
人为的例子:

  • 您有一个带有构造函数的类,该构造函数构造给定大小的字符串。你有一个函数(以及一个重载),你调用(当你实际打算调用时)。您希望它打印“3”,但它打印的是长度为 3 的空字符串。MyStringprint(const MyString&)print (char *string)print(3)print("3")

评论

250赞 maccullt 9/24/2008
写得不错,您可能想提到带有默认参数的多参数 ctor 也可以充当单个参数 ctor,例如 Object( const char* name=NULL, int otype=0)。
579赞 Michael Burr 8/27/2009
我认为还应该提到的是,应该考虑最初(或多或少自动)使单个参数构造函数显式化,并且仅在设计需要隐式转换时才删除显式关键字。我认为构造器应该默认使用“隐式”关键字进行显式,以使它们能够作为隐式转换工作。但事实并非如此。
12赞 Christian Severin 6/17/2011
@thecoshman:你没有声明一个参数——你声明了一个构造函数。但是,是的:你的类型参数必须被构造,它们不会通过仅仅将其构造函数的参数插入函数来静默构造。explicitexplicitFooexplicite
98赞 Larry 7/10/2012
仅供参考,在您的示例中调用“print(3)”时,该函数需要为“print(const MyString &”)。“const”在这里是强制性的,因为 3 被转换为临时的“MyString”对象,你不能将临时对象绑定到引用,除非它是“const”(一长串 C++ 陷阱中的另一个)
57赞 Arbalest 12/15/2012
为了完整起见,我补充一点,除了参数转换之外,这里的显式关键字还将阻止使用复制 ctor 的赋值形式(例如,Foo myFoo = 42;),并且需要显式形式 Foo myFoo = Foo(42);或 Foo myFoo(42);
1333赞 Eddie 9/23/2008 #2

假设,你有一个类: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'intString(int)explicit

class String {
public:
    explicit String (int n); //allocate n bytes
    String(const char *p); // initialize sobject with string p
};

评论

18赞 Johannes Schaub - litb 12/13/2010
值得注意的是,C++0x 的新通用初始化规则将格式错误,而不是尝试使用空指针调用其他构造函数,就像那样。String s = {0};String s = 0;
11赞 Arbalest 8/5/2013
尽管这是一个老问题,但似乎值得指出一些事情(或有人让我直截了当)。通过使 int 形式或两个 ctor “显式”,如果您在意思中使用时,您仍然会遇到相同的错误,不是吗?此外,从上面的评论中,我看到了 over 的改进行为,这要归功于使 ctor 的 int 形式“显式”。但是,除了知道 ctor 的优先级之外,您如何知道这样做的意图(即如何发现错误)?String mystring('x')String mystring("x")String s = {0}String s = 0String s{0}
1赞 Sreeraj Chundayil 3/23/2015
为什么 String mystring = 'x';是否正在转换为 int?
14赞 DavidRR 4/30/2015
@InQusitive:被视为整数,因为 char 数据类型只是一个 1 字节的整数'x'
11赞 Géry Ogam 8/12/2015
您的示例的问题在于它仅适用于复制初始化(使用 ),而不适用于直接初始化(不使用 ):如果您编写 ,编译器仍将调用构造函数而不会生成错误,正如@Arbalest指出的那样。该关键字用于防止在直接初始化和函数解析中发生的隐式转换。对于您的示例,更好的解决方案是构造函数的简单重载:。==String(int)String mystring('x');explicitString(char c);
200赞 cjm 9/24/2008 #3

在 C++ 中,只有一个必需参数的构造函数被视为隐式转换函数。它将参数类型转换为类类型。这是否是一件好事取决于构造函数的语义。

例如,如果你有一个带有 constructor 的字符串类,这可能正是你想要的。您可以将 a 传递给需要 的函数,编译器将自动为您构造一个临时对象。String(const char* s)const char*StringString

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

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

那边

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

成为编译时错误。如果要传递临时对象,则必须显式执行此操作:Buffer

useBuffer(Buffer(4));

总之,如果单参数构造函数将参数转换为类的对象,则可能不想使用关键字。但是,如果你有一个构造函数,它只是碰巧采用一个参数,你应该声明它,以防止编译器因意外的转换而让你感到惊讶。explicitexplicit

评论

10赞 pqnet 6/26/2017
useBuffer期望他的论点有一个 lvalue,因此也行不通。将其更改为 take a or or just 会使其工作。useBuffer(Buffer(4))const Buffer&Buffer&&Buffer
24赞 fmuecke 10/2/2009 #4

如前所述,使一个参数构造函数(包括具有 ,,... 默认值的构造函数)始终是一种很好的编码实践。 就像 C++ 一样:如果你不这样做 - 你会希望你这样做......arg2arg3

类的另一个好做法是将副本构造和赋值设为私有(也称为禁用它),除非您确实需要实现它。这样可以避免在使用 C++ 默认为您创建的方法时具有指针的最终副本。执行此操作的另一种方法是派生自 。boost::noncopyable

评论

26赞 v010dya 10/2/2015
这篇文章写于2009年。今天,您不将它们声明为私有,而是说.= delete
1赞 Will 7/6/2023
你的第一句话中缺少“明确”这个词吗?它应该读作“如前所述明确”吗?
50赞 SankararaoMajji 11/21/2012 #5

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

38赞 Helixirr 5/14/2013 #6

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

class C {
public:
    explicit C() =default;
};

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

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

-keyword 也可以在用户定义的类型转换运算符中使用:explicit

class C{
public:
    explicit inline operator bool() const {
        return true;
    }
};

int main() {
    C c;
    bool b = static_cast<bool>(c);
    return 0;
}

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

评论

2赞 Justin Time - Reinstate Monica 2/11/2016
explicit operator bool()也是安全布尔值的 C++11 版本,可以在条件检查中隐式使用(据我所知,只能在条件检查中使用)。在第二个示例中,此行在 : 中也有效。但是,除此之外,如果没有显式强制转换,它就无法使用。main()if (c) { std::cout << "'c' is valid." << std:: endl; }
0赞 curiousguy 6/13/2018
"要显式调用的构造函数“ 否
0赞 curiousguy 6/13/2018
@JustinTime 这是一个疯狂的、破碎的保险箱版本。显式隐式转换的整个想法是荒谬的。
0赞 Justin Time - Reinstate Monica 6/19/2018
@curiousguy 真的。它看起来有点像一个笨拙的东西,旨在更容易记住(可能希望将其翻译成经常使用),而不是遵循英语逻辑,并且旨在不与以前的安全 bool 实现完全不兼容(所以如果你把它换进去,你就不太可能破坏某些东西)。至少是 IMO。
0赞 JDługosz 11/19/2021
我编辑了它以修复并在编译器资源管理器中玩了一下,似乎制作默认构造函数实际上并没有做任何事情。这个答案是完全错误的,即使已经进行了代码更正。C c();explicit
59赞 Gautam 10/8/2013 #7

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

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

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

private:
    int m_x;
};

可以通过 2 种方式创建 Foo 类的对象:

Foo bar1(10);

Foo bar2 = 20;

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

通常最好将单参数构造函数声明为 ,除非您的实现明确禁止它。explicit

另请注意,构造函数

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

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

例如,如果您要创建一个函子(查看答案中声明的“add_x”结构),则故意不想显式使用单参数构造函数。在这种情况下,创建一个对象可能是有意义的。add_x add30 = 30;

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

9赞 Konstantin Burlachenko 1/23/2015 #8

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

在 C++11 中,您还可以使用此类关键字指定 “operator type()”http://en.cppreference.com/w/cpp/language/explicit 有了这样的规范,你就可以在显式转换和对象的直接初始化方面使用运算符。

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

  • 向上积分秩(char 到 int,float 到 double);
  • 标准转换(int 到 double);
  • 将对象的指针转换为基类和 void*;
67赞 Pixelchemist 7/11/2015 #9

关键字随附explicit

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

C++ [class.conv.ctor]

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

2) 显式构造函数构造对象与非显式构造函数一样,但仅在显式使用直接初始化语法 (8.5) 或强制转换 (5.2.9, 5.4) 的情况下才这样做。默认构造函数可以是显式构造函数;此类构造函数将用于执行 default-initialization 或 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, Zfoo, bar, baz

让我们看一下结构和函数的小设置,看看转换和非转换之间的区别。explicitexplicit

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

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

考虑一个结构,可转换为 ,一个隐式构造的结构,以及一个重载的函数。VintUVfUbool

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

struct U { U(V) { } };

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

如果传递 类型的对象,则调用 是模棱两可的。fV

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

编译器不知道是使用 的构造函数还是转换函数将对象转换为要传递给的类型。UVf

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

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

考虑一个打印某个向量的函数:

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

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

print_intvector(3);

人们会从这样的电话中得到什么?一行包含或三行包含?(第二个是发生的事情。30

在类接口中使用 explicit 关键字可强制接口用户明确了解所需的转换。

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

如果你明白你的意思,就要明确。

评论

1赞 stu 8/14/2022
“如果你明白你的意思,就要明确。”多么棒的报价。提醒我为什么自动再次存在?:-)
0赞 Jeremy Friesner 2/26/2023
@stu auto 的存在是为了让C++更容易编写,更难阅读:(
0赞 stu 2/27/2023
这很好,直到你成为继承系统的人。
32赞 selfboot 8/20/2016 #10

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
}

评论

1赞 curiousguy 6/13/2018
explicit operator bool()与。 是一个特例。没有办法用用户定义的 和一个名为 的函数来重现它。ifBoolexplicit operator Bool()If
-5赞 Manojkumar Khotele 4/19/2022 #11

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

除了“delete”关键字,“explicit”还允许您控制编译器生成特殊成员函数的方式 - 默认构造函数、复制构造函数、复制赋值运算符、析构函数、移动构造函数和移动赋值。

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

评论

0赞 Alex Vergara 10/7/2022
这甚至不是一个答案。此外,关键字不允许您控制编译器生成特殊成员函数的方式delete