现在的位置: 首页 > 综合 > 正文

explicit Constructors——显式构造函数

2013年12月01日 ⁄ 综合 ⁄ 共 5026字 ⁄ 字号 评论关闭

首先引用一段《ANSI/ISO C++Professional Programmer'sHandbook》中的解释:

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  version 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++ 中, 如果一个类有只有一个参数的构造函数,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++的一个特性。
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’

 

抱歉!评论已关闭.