显式关键字是什么意思?

Skizz 2753 c++ constructor explicit c++-faq explicit-constructor

explicit关键字在C++中意味着什么?

Skizz.. 3165

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

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

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的空字符串.

  • 我认为还应该提到的是,应该考虑最初使单个参数构造函数显式化(或多或少自动),并且仅在需要隐式转换*时才删除显式关键字*.我认为默认情况下,构造函数应该是显式的,带有'implicit'关键字,以使它们能够作为隐式转换.但事实并非如此. (459认同)
  • 很好的写,你可能想提到带有默认参数的多arg ctors也可以作为单个arg ctor,例如,Object(const char*name = NULL,int otype = 0). (177认同)
  • 只是一个在你的例子中调用"print(3)"的FYI,该函数需要是"print(const MyString&")."const"在这里是必需的,因为3被转换为临时的"MyString"对象,你不能将临时绑定到引用,除非它是"const"(在C++陷阱的长列表中是另一个) (80认同)
  • 为了完整起见,我补充说,除了参数转换之外,这里的_explicit_关键字还将阻止使用复制ctor的赋值形式(例如,Foo myFoo = 42;)并且需要显式形式Foo myFoo = Foo(42) ; 或Foo myFoo(42); (41认同)
  • @thecoshman:你没有声明一个_parameter_`explicit`--你声明一个_constructor_`explicit`.但是是的:你的`Foo`类型的参数必须构造为`explicite`ly,它们不会通过将它们的构造函数的参数插入函数来静默构造. (9认同)
  • @Rushil:是的,我知道这是*预期的*行为,但你知道隐式转换,因为我已经告诉过你了.如果这是你在寻找的唯一代码(做不熟悉的代码的一些维护,说),那么你就需要更多的代码,发现了隐式转换来解释得到输出,而不是`5`五个空格的原因.在python中,这将输出'5'而不是'',但在这里它是另一种方式.我想说的是隐式转换对用户是隐藏的,因此是导致意外结果的原因. (7认同)
  • @meteors:`:m_foo(foo)`是一个初始化列表,仅对构造函数有效,注意函数的名称与类相同.你可以随意调用`DoBar(42)`.假设你有一个函数`void Thing(Class1 c);`和Class1有一个构造函数`Class1(Class2 d)`而Class2有一个构造函数`Class2(int e)`然后调用`Thing(314)`将无效为参数需要两个隐式转换,第一个从int到Class2,第二个从Class2到Class1. (5认同)
  • @ThePcLuddite:我的意思是编译器不喜欢转换而不是进行转换,因此如果有一个选项可以避免这样做(例如,如果有一个重载的函数选择)并且它赢了,它就不会进行转换永远不会将转换链接在一起(即创建一个中间对象).我不认为它会产生错误.有时英语是如此模糊的语言!嗯,如果一个对象可以转换成多个对象之一并且每个对象都是有效类型(通过重载)会发生什么.我觉得这是一个错误,但需要检查. (3认同)
  • @deimus:'void main()'不是合法的C/C++. (2认同)

小智.. 1094

假设你有一个班级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; `会的. (15认同)
  • @InQusitive:''x'被视为整数,因为[`char`数据类型只是一个1字节的整数](http://stackoverflow.com/a/5030541/1497596). (10认同)
  • 您的示例的问题是它只适用于*copy initialization*(使用`=`)但不适用于*direct initialization*(不使用`=`):编译器仍将调用`String(int)`构造函数而不使用如果你写'String mystring('x');`,就像@Arbalest指出的那样产生一个错误.`explicit`关键字用于防止在直接初始化和函数解析中发生的隐式转换.对你的例子更好的解决方案是构造函数的简单重载:`String(char c);`. (9认同)
  • 即使这是一个古老的问题,似乎值得指出一些事情(或让某人让我直截了当).通过使用int形式或两个ctors,'explicit'如果你使用`String mystring('x')`,你的意思是`String mystring("x")`你不是吗?另外,从上面的注释中我看到`String s = {0}`比`String s = 0`的改进行为,这要归功于使ctor'显式'的int形式.但是,除了知道ctors的优先级之外你怎么知道这个`String s {0}`的意图(即如何发现bug)? (8认同)

cjm.. 153

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

例如,如果你有一个带有构造函数的字符串类String(const char* s),那可能就是你想要的.您可以将a传递const char*给期望a的函数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`会使它工作. (6认同)

小智.. 43

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

显式函数说明符控制不需要的隐式类型转换.它只能用于类声明中的构造函数声明.例如,除了默认构造函数之外,以下类中的构造函数是转换构造函数.

class A
{
public:
    A();
    A(int);
    A(const char*, int = 0);
};

以下声明是合法的:

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

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

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

例如,如果您将类声明为:

class A
{
public:
    explicit A();
    explicit A(int);
    explicit A(const char*, int = 0);
};

您只能分配与类类型值匹配的值.

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

  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);

  • 这是来自https://www.ibm.com/support/knowledgecenter/zh-CN/ssw_ibm_i_73/rzarg/explicit_conversion_constructors.htm的直接副本 (2认同)

Gautam.. 41

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

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

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;可能有意义的对象.

是对显式构造函数的一个很好的写作.


Pixelchemist.. 39

关键字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:

让我们看一下结构和函数的小型设置,以查看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转换函数或构造函数?

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

考虑一种结构V,可转换到int,一个结构U从隐式constructible V以及功能f重载Ubool分别.

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或转换功能Vexplicit,将不会有歧义,因为只有非显式转换将被考虑.如果两者都是显式的,则必须使用显式转换或强制转换操作来完成对使用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所述(在"The C++ Programming Language",第4版,35.2.1,第1011页)中提出的问题为什么std::duration不能用普通数字隐式构造:

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


小智.. 38

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


Helixirr.. 27

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中标准化.

  • `C c();`在第一个例子中并不意味着你的意思:它是一个名为`c`的函数的声明,它不带参数并返回一个`C`的实例. (15认同)
  • `explicit operator bool()`也是安全bool的C++ 11版本,可以在条件检查中隐式使用(在条件检查中_only_,据我所知).在你的第二个例子中,这一行在`main()`中也是有效的:`if(c){std :: cout <<"'c'是有效的." << std :: endl; }`.但是,除此之外,如果没有明确的铸造,它就无法使用. (2认同)

selfboot.. 20

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
}


fmuecke.. 18

这已经讨论过(什么是显式构造函数).但我必须说,它缺乏这里的详细描述.

此外,如上所述,使用一个参数构造函数(包括那些具有arg2,arg3,...的默认值的构造函数)总是一个很好的编码实践.像C++一样:如果你不这样做 - 你会希望你做到......

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

  • 这篇文章写于2009年.今天你不要将它们声明为私有,而是说`= delete`. (8认同)

bruziuz.. 5

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

在C ++ 11中,您还可以使用关键字http://en.cppreference.com/w/cpp/language/explicit指定“ operator type()”,通过这种规范,您可以在显式转换方面使用operator,并且直接初始化对象。

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

  • 提升积分等级(char到int,float到double);
  • 标准转换(从int到double);
  • 将对象的指针转换为基类并转换为void *;


归档时间:

查看次数:

808647 次

最近记录:

2 年,3 月 前