【问题标题】:What does the explicit keyword mean?显式关键字是什么意思?
【发布时间】:2010-09-12 09:05:34
【问题描述】:

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

【问题讨论】:

  • 我只想指出自 C++11 以来出现的任何新人,explicit 不仅可以应用于构造函数。它现在在应用于转换运算符时也有效。假设您有一个类 BigInt 带有一个到 int 的转换运算符和一个到 std::string 的显式转换运算符,无论出于何种原因。你可以说int i = myBigInt;,但你必须明确地转换(最好使用static_cast)才能说std::string s = myBigInt;。
  • 不能明确也指赋值吗? (即int x(5);)
  • @curiousguy,不存在显式隐式转换。
  • @curiousguy,它本质上不是隐式转换。将explicit 放在那里声明了到类型的显式转换。该过程不涉及任何隐含性。
  • @Milan,是的,就是这样。如果您正在寻找更多信息,this answer 会更正式地写出来。请注意bool is special in this regard。这些答案和搜索“显式转换运算符”将引导您获得更多关于此功能的文章,并且比评论链更适合。

标签: c++ constructor explicit c++-faq explicit-constructor


【解决方案1】:

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

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

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 类,其构造函数构造一个给定大小的字符串。你有一个函数print(const MyString&)(以及一个重载print (char *string)),你调用print(3)(当你实际上打算调用print("3")时)。您希望它打印“3”,但它会打印一个长度为 3 的空字符串。

【讨论】:

  • 写得好,您可能想提到具有默认参数的多参数 ctor 也可以充当单个 arg ctor,例如 Object( const char* name=NULL, int otype=0)。
  • 我认为还应该提到的是,应该首先考虑使单参数构造函数显式(或多或少自动),并且仅在需要隐式转换时才删除显式关键字按设计。我认为默认情况下,构造函数应该是显式的,并带有一个“隐式”关键字,以使它们能够作为隐式转换工作。但事实并非如此。
  • @thecoshman:你没有声明一个参数 explicit——你声明了一个构造函数 explicit。但是是的:Foo 类型的参数必须构造 explicitely,它们不会通过将构造函数的参数插入函数来静默构造。
  • 仅供参考,在您的示例中调用“print(3)”时,该函数需要为“print(const MyString &”)。 “const”在这里是强制性的,因为 3 被转换为临时的“MyString”对象,除非它是“const”,否则不能将临时对象绑定到引用(C++ 陷阱中的另一个)
  • 为了完整起见,我补充说,除了参数转换之外,这里的 explicit 关键字还将阻止使用复制 ctor 的赋值形式(例如,Foo myFoo = 42;) 并要求显式形式 Foo myFoo = Foo(42);或 Foo myFoo(42);
【解决方案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' 将被隐式转换为int,然后String(int) 构造函数将被调用。但是,这可能不是用户想要的。所以,为了防止这种情况,我们将构造函数定义为explicit:

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

【讨论】:

  • 值得注意的是,C++0x 的新通用初始化规则将使String s = {0}; 格式错误,而不是像String s = 0; 那样尝试使用空指针调用另一个构造函数做。
  • 尽管这是一个古老的问题,但似乎值得指出一些事情(或让某人直言不讳)。通过将 int 形式或两个 ctor 设为“显式”,如果您使用 String mystring('x') 而您的意思是 String mystring("x"),您仍然会遇到相同的错误,不是吗?此外,从上面的评论中,我看到 String s = {0} 的行为比 String s = 0 有所改进,这要归功于将 ctor 的 int 形式设为“显式”。但是,除了知道 ctors 的优先级之外,您如何知道这个 String s{0} 的意图(即如何发现错误)?
  • 为什么 String mystring = 'x';正在转换为 int?
  • @InQusitive:'x' 被视为整数,因为 char data type is just a 1-byte integer。
  • 你的例子的问题是它只适用于复制初始化(使用=)但不适用于直接初始化(不使用@ 987654340@):正如@Arbalest 指出的那样,如果您编写String mystring('x');,编译器仍将调用String(int) 构造函数而不会产生错误。 explicit 关键字用于防止在直接初始化和函数解析中发生的隐式转换。您的示例的更好解决方案是构造函数的简单重载:String(char c);.
【解决方案3】:

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

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

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

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

这样,

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

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

useBuffer(Buffer(4));

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

【讨论】:

  • useBuffer 期望他的论点有一个左值,useBuffer(Buffer(4)) 也因此不起作用。将其更改为采用 const Buffer& 或 Buffer&& 或只是 Buffer 将使其工作。
【解决方案4】:

如前所述,使您的单参数构造函数(包括具有arg2、arg3、...的默认值的构造函数)始终是一种很好的编码习惯。 像往常一样使用 C++:如果你不这样做 - 你会希望你这样做......

另一个类的好习惯是让复制构造和赋值私有(也就是禁用它),除非你真的需要实现它。这样可以避免在使用 C++ 默认为您创建的方法时最终获得指针副本。另一种方法是派生自boost::noncopyable。

【讨论】:

  • 这篇文章写于 2009 年。今天你不声明它们是私有的,而是说 = delete。
【解决方案5】:

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

【讨论】:

  • 如果你懂 c++,直接回答,如果你不懂,你可以去 cjm 的回答...
【解决方案6】:

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

【讨论】:

  • 第一个示例中的 C c(); 并不意味着您认为它的含义:它是一个名为 c 的函数的声明,它不接受任何参数并返回一个 C 的实例。跨度>
  • explicit operator bool() 也是安全布尔的 C++11 版本,可以在条件检查中隐式使用(据我所知,仅在条件检查中)我知道)。在您的第二个示例中,此行在main() 中也有效:if (c) { std::cout &lt;&lt; "'c' is valid." &lt;&lt; std:: endl; }。但是,除此之外,它不能在没有显式转换的情况下使用。
  • "要显式调用的构造函数" 否
  • @JustinTime 这是安全布尔的一个愚蠢的、损坏的版本。显式隐式转换的整个想法是荒谬的。
  • @curiousguy 是的。它看起来有点像一个杂物,旨在更容易记住(可能是希望翻译成经常使用的)而不是遵循英语逻辑,并且旨在与以前的安全 bool 实现不完全不兼容(所以你更少如果你换掉它可能会破坏它)。至少是国际海事组织。
【解决方案7】:

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

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

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' 结构在this 答案中)。在这种情况下,将对象创建为add_x add30 = 30; 可能是有意义的。

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

【讨论】:

    【解决方案8】:

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

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

    附:当使用由用户定义的转换(通过构造函数和类型转换运算符)时,只允许使用一级隐式转换。 但您可以将此转换与其他语言转换结合使用

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

    【讨论】:

      【解决方案9】:

      关键字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 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 转换函数或构造函数?

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

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

      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'; }
      

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

      print_intvector(3);
      

      人们对这样的电话有何期待?一行包含3 还是三行包含0? (第二个是发生了什么。)

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

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

      如果您知道自己的意思,请明确说明。

      【讨论】:

        【解决方案10】:

        Cpp 参考总是有帮助的!!!可以在here 找到有关显式说明符的详细信息。您可能还需要查看implicit conversions 和copy-initialization。

        快速浏览

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

        【讨论】:

        • explicit operator bool() 与 if 是一个特例。无法使用用户定义的Bool、explicit operator Bool() 和一个名为If 的函数来重现它。
        猜你喜欢
        • 2011-02-14
        • 2010-10-29
        • 2011-10-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多