1.什么时候编译器会给产生一个默认构造函数?
我们经常听到这样的说法,如果用户没有显示地给出构造函数,则编译器会给合成一个默认的构造函数。事实是这样的吗?不是!有四种情况会导致“编译器必须为未声明constructor之classes合成一个default constructor”,这四种情况以外我们说它们拥有的是implict trivial default constructor,它们实际上并不会被合成出来。
(1)“带有Default Constructor”的Member Class Object
这里描述的其实是一种类与类之间的内含关系。
举个例子如下:
class Foo
{
public:
Foo()
{ };
...
}
class Bar
{
public:
Foo foo;
int ival;
...
};
请注意:
1.1被合成的default constructor只满足编译器的需要,而不是程序的需要。
1.2如果已经存在constructors,编译器会扩张它们,即在user code被执行之前先调用必要的default constructors.
扩张后的constructor可能像这样(假设我们修改了上面的代码,给出了Bar的显示构造函数):
Bar::Bar()
{
foo.Foo::Foo(); //编译器附加上compiler code
ival = 0; //显示的用户代码
}
1.3C++语言要求以“member objects在class中的声明次序”来调用各个constructor.
(2)“带有Default Constructor”的Base Class
如果设计者提供多个constructor,但其中都没有default constructor呢?编译器会扩张每个constructors。
(3)“带有一个Virtual Funciton”的class
要在default constructor里设置vptr,vtbl。
(4)“带有一个Virtual Base Class”的class
要设置vbptr,vbtbl。
详情请参考:《深度探索C++对象模型》第2章The Semantics of Cosntructors
2.默认构造函数与单参数构造函数陷阱
2.1避免不必要的默认构造函数
如果没有默认构造函数,你可能无法在堆空间上申请对象数组,你可能也无法应用一些设计不好的模板,如果有虚基类,则要求所有由它继承下来的派生类无论多远都必须知道理解虚基类构造函数的参数的含义并且提供这些参数。。。基于此,也许你认为所有的类都应该有默认构造函数,即使默认构造函数没有足够的信息来完全地初始化一个对象。这个原则的拥护者会提供如下的类:
class A
{
public:
A(int ival = 0)
{};
};
即通过默认值来解决。但是这样做会使得其他成员函数变得复杂,因为这不再确保类A对象的数据项进行了有意义的初始化,进一步使得成员函数先去检测这些值是否有意义……这也就影响了类的运行效率。
详情参考:《More Effecitve C++》条款4,避免不必要的默认构造函数。
2.2单参构造函数的陷阱
#include <string>
#include <iostream>
using namespace std;
class Fruit //定义一个类,名字叫Fruit
{
string name; //定义一个name成员
string colour; //定义一个colour成员
public:
bool isSame(const Fruit &otherFruit) //期待的形参是另一个Fruit类对象,测试是否同名
{
return name == otherFruit.name;
}
void print() //定义一个输出名字的成员print()
{
cout<<colour<<" "<<name<<endl;
}
Fruit(const string &nst,const string &cst = "green"):name(nst),colour(cst){} //构造函数
Fruit(){}
};
int main()
{
Fruit apple("apple");
Fruit orange("orange");
cout<<"apple = orange ?: "<<apple.isSame(orange)<<endl; //没有问题,肯定不同
cout<<"apple = /"apple/" ?:"<<apple.isSame(string("apple")); //用一个string做形参?
system("pause");
return 0;
}
你可以运行一下上面的程序如果你的结果和运行结果一样,那么你就体会到了单参构造函数的危害----隐式转换!!!
解决的方法有两个:
一个是对单参构造函数使用关键字explicit,此时编译器要求必须显示构造对象。
第二个是使用代理类,即对上例中的name再进行一次封装,根据C++一条规则:这些隐式的转换序列不能包含多于一个的用户自定义类型转换,修改如下:
#include <string>
#include <iostream>
using namespace std;
class Fruit //定义一个类,名字叫Fruit
{
public:
class String
{
public:
String(string str):name(str)
{
}
string Name() const
{
return name;
}
private:
string name;
};
bool isSame(const Fruit &otherFruit) //期待的形参是另一个Fruit类对象,测试是否同名
{
return Sname.Name() == otherFruit.Sname.Name();
}
Fruit(const String &nst,const string &cst = "green"):Sname(nst),colour(cst){} //构造函数
private:
String Sname; //定义一个name成员
string colour; //定义一个colour成员
};
int main()
{
Fruit apple(Fruit::String("apple"));
Fruit orange(Fruit::String("orange"));
cout<<"apple = orange ?: "<<apple.isSame(orange)<<endl; //没有问题,肯定不同
cout<<"apple = /"apple/" ?:"<<apple.isSame(string("apple")); //用一个string做形参?
system("pause");
return 0;
}
这个例子改的还不够恰当,希望同僚们多多斧正。