c++中的explicit关键字用来修饰类的构造函数,表明该构造函数是显式的,既然有"显式"那么必然就有"隐式",那么什么是显示而什么又是隐式的呢?
如果c++类的构造函数有一个参数,那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象,如下面所示:
class MyClass { public: MyClass( int num ); } .... MyClass obj = 10; //ok,convert int to MyClass
|
在上面的代码中编译器自动将整型转换为MyClass类对象,实际上等同于下面的操作:
MyClass temp(10);
MyClass obj = temp;
上面的所有的操作即是所谓的"隐式转换".
如果要避免这种自动转换的功能,我们该怎么做呢?嘿嘿这就是关键字explicit的作用了,将类的构造函数声明为"显示",也就是在声明构造函数的时候 前面添加上explicit即可,这样就可以防止这种自动的转换操作,如果我们修改上面的MyClass类的构造函数为显示的,那么下面的代码就不能够编 译通过了,如下所示:
class MyClass { public: explicit MyClass( int num ); } .... MyClass obj = 10; //err,can't non-explict convert class isbn_mismatch:public std::logic_error{ public: explicit isbn_missmatch(const std::string &s):std:logic_error(s){} isbn_mismatch(const std::string &s,const std::string &lhs,const std::string &rhs): std::logic_error(s),left(lhs),right(rhs){} const std::string left,right; virtual ~isbn_mismatch() throw(){} }; Sales_item& operator+(const Sales_item &lhs,const Sales_item rhs) { if(!lhs.same_isbn(rhs)) throw isbn_mismatch("isbn missmatch",lhs.book(),rhs.book()); Sales_item ret(lhs); ret+rhs; return ret; } Sales_item item1,item2,sum; while(cin>>item1>>item2) { try{ sun=item1+item2; }catch(const isbn_mismatch &e) { cerr<<e.what()<<"left isbn is:"<<e.left<<"right isbn is:"<<e.right<<endl; } } |
用于用户自定义类型的构造函数,指定它是默认的构造函数,不可用于转换构造函数。因为构造函数有三种:1拷贝构造函数2转换构造函数3一般的构造函数(我自己的术语^_^)
另:如果一个类或结构存在多个构造函数时,explicit 修饰的那个构造函数就是默认的
class isbn_mismatch:public std::logic_error{ public: explicit isbn_missmatch(const std::string &s):std:logic_error(s){} isbn_mismatch(const std::string &s,const std::string &lhs,const std::string &rhs): std::logic_error(s),left(lhs),right(rhs){} const std::string left,right; virtual ~isbn_mismatch() throw(){} }; Sales_item& operator+(const Sales_item &lhs,const Sales_item rhs) { if(!lhs.same_isbn(rhs)) throw isbn_mismatch("isbn missmatch",lhs.book(),rhs.book()); Sales_item ret(lhs); ret+rhs; return ret; } Sales_item item1,item2,sum; while(cin>>item1>>item2) { try{ sun=item1+item2; }catch(const isbn_mismatch &e) { cerr<<e.what()<<"left isbn is:"<<e.left<<"right isbn is:"<<e.right<<endl; } } |
这个 《ANSI/ISO C++ Professional Programmer's Handbook 》是这样说的
explicit Constructors A constructor that takes a single argument is, by default, an implicit conversion operator, which converts its argument to an object of its class (see also Chapter 3, "Operator Overloading"). Examine the following concrete example: class string { private: int size; int capacity; char *buff; public: string(); string(int size); // constructor and implicit conversion operator string(const char *); // constructor and implicit conversion operator ~string(); }; Class string has three constructors: a default constructor, a constructor that takes int, and a constructor that constructs a string from const char *. The second constructor is used to create an empty string object with an initial preallocated buffer at the specified size. However, in the case of class string, the automatic conversion is dubious. Converting an int into a string object doesn't make sense, although this is exactly what this constructor does. Consider the following: int main() { string s = "hello"; //OK, convert a C-string into a string object int ns = 0; s = 1; // 1 oops, programmer intended to write ns = 1, } In the expression s= 1;, the programmer simply mistyped the name of the variable ns, typing s instead. Normally, the compiler detects the incompatible types and issues an error message. However, before ruling it out, the compiler first searches for a user-defined conversion that allows this expression; indeed, it finds the constructor that takes int. Consequently, the compiler interprets the expression s= 1; as if the programmer had written s = string(1); You might encounter a similar problem when calling a function that takes a string argument. The following example can either be a cryptic coding style or simply a programmer's typographical error. However, due to the implicit conversion constructor of class string, it will pass unnoticed: int f(string s); int main() { f(1); // without a an explicit constructor, //this call is expanded into: f ( string(1) ); //was that intentional or merely a programmer's typo? } 'In order to avoid such implicit conversions, a constructor that takes one argument needs to be declared explicit: class string { //... public: explicit string(int size); // block implicit conversion string(const char *); //implicit conversion ~string(); }; An explicit constructor does not behave as an implicit conversion operator, which enables the compiler to catch the typographical error this time: int main() { string s = "hello"; //OK, convert a C-string into a string object int ns = 0; s = 1; // compile time error ; this time the compiler catches the typo } Why aren't all constructors automatically declared explicit? Under some conditions, the automatic type conversion is useful and well behaved. A good example of this is the third constructor of string: string(const char *); The implicit type conversion of const char * to a string object enables its users to write the following: string s; s = "Hello"; The compiler implicitly transforms this into string s; //pseudo C++ code: s = string ("Hello"); //create a temporary and assign it to s On the other hand, if you declare this constructor explicit, you have to use explicit type conversion: class string { //... public: explicit string(const char *); }; int main() { string s; s = string("Hello"); //explicit conversion now required return 0; } Extensive amounts of legacy C++ code rely on the implicit conversion of constructors. The C++ Standardization committee was aware of that. In order to not make existing code break, the implicit conversion was retained. However, a new keyword, explicit, was introduced to the languageto enable the programmer to block the implicit conversion when it is undesirable. As a rule, a constructor that can be invoked with a single argument needs to be declared explicit. When the implicit type conversion is intentional and well behaved, the constructor can be used as an implicit conversion operator. |
网上找的讲的最好的贴:
C++ 中 explicit 关键字的作用
在 C++ 中, 如果一个类有只有一个参数的构造函数,C++ 允许一种特殊的声明类变量的方式。在这种情况下,可以直接将一个对应于构造函数参数类型的数据直接赋值给类变量,编译器在编译时会自动进行类型转换,将对应于构造函数参数类型的数据转换为类的对象。 如果在构造函数前加上 explicit 修饰词, 则会禁止这种自动转换,在这种情况下, 即使将对应于构造函数参数类型的数据直接赋值给类变量,编译器也会报错。
下面以具体实例来说明。
建立people.cpp 文件,然后输入下列内容:
class People { public: int age; explicit People (int a) { age=a; } }; void foo ( void ) { People p1(10); //方式一 People* p_p2=new People(10); //方式二 People p3=10; //方式三 } |
这段 C++ 程序定义了一个类 people ,包含一个构造函数, 这个构造函数只包含一个整形参数 a ,可用于在构造类时初始化 age 变量。
然后定义了一个函数foo,在这个函数中我们用三种方式分别创建了三个10岁的“人”。 第一种是最一般的类变量声明方式。第二种方式其实是声明了一个people类的指针变量,然后在堆中动态创建了一个people实例,并把这个实例的地址赋值给了p_p2.第三种方式就是我们所说的特殊方式,为什么说特殊呢? 我们都知道,C/C++是一种强类型语言,不同的数据类型是不能随意转换的,如果要进行类型转换,必须进行显式强制类型转换,而这里,没有进行任何显式的转换,直接将一个整型数据赋值给了类变量p3.
因此,可以说,这里进行了一次隐式类型转换,编译器自动将对应于构造函数参数类型的数据转换为了该类的对象,因此方式三经编译器自动转换后和方式一最终的实现方式是一样的。
不相信? 耳听为虚,眼见为实,让我们看看底层的实现方式。
为了更容易比较方式一和方式三的实现方式,我们对上面的代码作一点修改,去除方式二:
void foo ( void ) { People p1(10); //方式一 People p3=10; //方式三 } |
去除方式二的原因是方式二是在堆上动态创建类实例,因此会有一些额外代码影响分析。修改完成后,用下列命令编译 people.cpp
$ gcc -S people.cpp "-S"选项是GCC输出汇编代码。命令执行后,默认生成people.s。 关键部分内容如下: .globl _Z3foov .type _Z3foov, @function _Z3foov: .LFB5: pushl %ebp .LCFI2: movl %esp, %ebp .LCFI3: subl $24, %esp .LCFI4: movl $10, 4(%esp) leal -4(%ebp), %eax movl %eax, (%esp) call _ZN6PeopleC1Ei movl $10, 4(%esp) leal -8(%ebp), %eax movl %eax, (%esp) call _ZN6PeopleC1Ei leave ret |
看“.LCFI4” 行后面的东西,1-4行和5-8行几乎一模一样,1-4行即为方式一的汇编代码,5-8即为方式三的汇编代码。 细心的你可能发现2和6行有所不同,一个是 -4(%ebp) 而另一个一个是 -8(%ebp) ,这分别为类变量P1和P3的地址。
对于不可随意进行类型转换的强类型语言C/C++来说, 这可以说是C++的一个特性。哦,今天好像不是要说C++的特性,而是要知道explicit关键字的作用?
explicit关键字到底是什么作用呢? 它的作用就是禁止这个特性。如文章一开始而言,凡是用explicit关键字修饰的构造函数,编译时就不会进行自动转换,而会报错。
让我们看看吧! 修改代码:
class People { public: int age; explicit People (int a) { age=a; } }; |
然后再编译:
$ gcc -S people.cpp
编译器立马报错:
people.cpp: In function ‘void foo()’:
people.cpp:23: 错误:请求从 ‘int’ 转换到非标量类型 ‘People’