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


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


2371
2017-09-23 13:58


起源


我只想指出自C ++ 11以来的任何新人, explicit 可以应用于不仅仅是构造函数。它现在也适用于转换运算符。说你有课 BigInt 使用转换运算符 int 和一个显式转换运算符 std::string 无论出于何种原因你可以说 int i = myBigInt;,但你必须明确地投射(使用 static_cast,最好)为了说 std::string s = myBigInt;。 - chris
不明确也可以参考作业? (即 int x(5);) - Eitan Myron
theunixshell.blogspot.com/2013/01/explicit-keyword-in-c.html - Vijay
@chris显式隐式转换的想法是荒谬的。保持清醒! - curiousguy
@curiousguy,没有明确的隐式转换。 - chris


答案:


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

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

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 所以这个构造函数可以用来将参数转换为正确的类型。

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

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

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

  • 你有一个 MyString(int size) 带有构造函数的类,该构造函数构造给定大小的字符串。你有一个功能 print(const MyString&),你打电话 print(3) (当你 其实 打算打电话 print("3"))。你希望它打印“3”,但它打印一个长度为3的空字符串。

2755
2017-09-23 14:09



很好的写,您可能想提到具有默认参数的多arg ctors也可以充当单个arg ctor,例如Object(const char * name = NULL,int otype = 0)。 - maccullt
我认为还应该提到的是,应该考虑最初使单个参数构造函数显式化(或多或少自动),并且仅在需要隐式转换时才删除显式关键字 按设计。我认为默认情况下,构造函数应该是显式的,带有'implicit'关键字,以使它们能够作为隐式转换。但事实并非如此。 - Michael Burr
@thecoshman:你没有申报 参数  explicit  - 你宣布一个 构造函数  explicit。但是:您的类型参数 Foo 必须建造 explicite因为,只需将构造函数的参数插入函数中,就不会构建它们。 - Christian Severin
只是一个在你的例子中调用“print(3)”的FYI,该函数需要是“print(const MyString&”)。 “const”在这里是必需的,因为3被转换为临时的“MyString”对象,你不能将临时绑定到引用,除非它是“const”(在C ++陷阱的长列表中是另一个) - Larry
为了完整起见,我补充说除了参数转换之外 明确的 这里的关键字也会阻止使用复制文件的赋值形式(例如,Foo myFoo = 42;)并要求显式格式Foo myFoo = Foo(42);或Foo myFoo(42); - Arbalest


假设你有一个班级 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
};

964
2017-09-23 16:37



值得注意的是,C ++ 0x的新通用初始化规则将会产生 String s = {0}; 格式错误,而不是试图用空指针调用其他构造函数 String s = 0; 会做。 - Johannes Schaub - litb
即使这是一个古老的问题,似乎值得指出一些事情(或让某人让我直截了当)。通过使int形式,或两个ctors,'显式',如果你使用,你仍然会有相同的错误 String mystring('x') 当你的意思 String mystring("x") 不是吗?另外,从上面的评论我看到改进的行为 String s = {0} 过度 String s = 0 感谢使ctor'显式'的int形式。但是,除了知道ctors的优先权之外你怎么知道这个的意图(即如何发现bug) String s{0} ? - Arbalest
为什么String mystring ='x';正在转换为int? - InQusitive
@InQusitive: 'x'被视为整数,因为 char 数据类型只是一个1字节的整数。 - DavidRR
您的示例的问题是它只适用于 复制初始化 (使用 =)但不是 直接初始化 (不使用 =):编译器仍会调用 String(int) 如果你写的话,构造函数不会产生错误 String mystring('x');,正如@Arbalest指出的那样。该 explicit keyword用于防止在直接初始化和函数解析中发生的隐式转换。对你的例子更好的解决方案是构造函数的简单重载: String(char c);。 - Maggyero


在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防止编译器意外转换让您感到惊讶。


130
2017-10-08 14:43



useBuffer期待他的论点的价值, useBuffer(Buffer(4)) 因为它不会起作用。改变它采取 const Buffer& 要么 Buffer&& 要不就 Buffer 会让它发挥作用。 - pqnet


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

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

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

这里 对显式构造函数是一个很好的写作。


34
2017-11-21 02:36





explicit keyword使转换构造函数成为非转换构造函数。因此,代码不易出错。


31
2017-07-10 23:48



如果你知道c ++就直接回答,如果不是你可以去cjm的回答...... - n611x007


关键字 explicit 伴随着

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

C ++ [class.conv.ctor]

1)在没有函数说明符explicit的情况下声明的构造函数指定从其参数类型到其类类型的转换。这样的构造函数称为转换构造函数。

2)显式构造函数与非显式构造函数一样构造对象,但仅在显式使用直接初始化语法(8.5)或强制转换(5.2.9,5.4)的情况下才这样做。默认构造函数可以是显式构造函数;这样的构造函数将用于执行默认初始化或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, 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 隐含地可构建 V 和一个功能 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'; }

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

print_intvector(3);

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

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

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

如果你知道你的意思,请明确说明。


31
2018-05-14 09:28





explicit-keyword可用于强制执行要调用的构造函数 明确地

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

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

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

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

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-keyword只强制显式转换为有效,所以 bool b = c; 在这种情况下,将是无效的演员。在这样的情况下 explicit-keyword可以帮助程序员避免隐式的,非预期的强制转换。这种用法已经标准化了 C ++ 11


25
2017-10-01 22:00



C c(); 在第一个例子中并不意味着你认为它意味着什么:它是一个名为的函数的声明 c 不带参数并返回实例 C。 - 6502
explicit operator bool() 也是安全bool的C ++ 11版本,可以在条件检查中隐式使用(和 只要 在条件检查中,据我所知)。在您的第二个示例中,此行也将有效 main(): if (c) { std::cout << "'c' is valid." << std:: endl; }。但是,除此之外,如果没有明确的铸造,它就无法使用。 - Justin Time
“要显式调用的构造函数“不 - curiousguy
@JustinTime这是安全布尔的一个破碎的版本。显性隐式转换的整个想法是荒谬的。 - curiousguy
@curiousguy真的。它似乎有点像一个kludge,更多的是容易记住(可能希望转换为经常使用)而不是遵循英语逻辑,并且设计为不与以前的安全bool实现完全不兼容(所以你少了如果换掉它可能会破坏某些东西)。 IMO,至少。 - Justin Time