C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(2)
5、缺省构造函数
缺省构造函数不需要参数,也叫做零参数构造函数。
5.1、什么时候需要缺省构造函数
考虑一下对象数组。生成对象数组的行为完成了两项任务:对所有的对象分配了连续的内存空间,对每个对象调用缺省构造函数。c++没有提供让数组生成代码直接调用不同构造函数的功能。例如,如果没有提供SpreadsheetCell类的缺省构造函数,下面的代码编译不会成功:
SpreadsheetCell cells[3]; // FAILS compilation without a default constructorSpreadsheetCell* myCellp{ new SpreadsheetCell[10] }; // also FAILS
可以像下面这样通过使用初始化来规避这个限制:
SpreadsheetCell cells[3]{ SpreadsheetCell{ 0 }, SpreadsheetCell{ 23 },SpreadsheetCell{ 41 } };
然而,如果你想要生成该类的对象数组,通常确保类拥有缺省构造函数相对容易一些。如果没有定义构造函数,编译器会自动为你生成一个缺省构造函数。编译器生成的构造函数后面我们再讨论。
5.2、如何书写缺省构造函数
下面是带有缺省构造函数的SpreadsheetCell类的定义的一部分:
export class SpreadsheetCell
{
public:SpreadsheetCell();// Remainder of the class definition omitted for brevity
};
下面是一个缺省构造函数的快速实现:
SpreadsheetCell::SpreadsheetCell()
{m_value = 0;
}
如果使用类内成员初始化来初始化m_value,这一行代码也可以省略。
SpreadsheetCell::SpreadsheetCell()
{
}
在栈上使用缺省构造函数如下:
SpreadsheetCell myCell;// Or// SpreadsheetCell myCell { }; // Calls the default constructor.myCell.setValue(6);println("cell 1: {}", myCell.getValue());
上面的代码生成了一个叫做myCell的新的SpreadsheetCell对象,赋值,打印值。不像其它栈上对象的构造函数,不需要用函数调用的语法来调用缺省构造函数。基于其它构造函数的语法,你可能想这样来调用缺省构造函数:
SpreadsheetCell myCell(); // WRONG, but will compile.
myCell.setValue(6);
// However, this line will not compile.
println("cell 1: {}", myCell.getValue());
很不幸,尝试调用缺省构造函数的那一行编译会通过。而接下来的那一行编译不成功。这个问题就是著名的最令人烦恼的解析,意思是编译器主认为第一行实际上是一个函数声明,该函数的名字为myCell,不带有参数并且返回SpreadsheetCell对象。当走到第二行时,它会认为你想用函数名当对象使!
当然了,可以不用函数调用风格的括号,使用统一的初始化语法如下:
SpreadsheetCell myCell { }; // Calls the default constructor.
警告:当在栈上生成带有缺省构造函数的对象时,要么使用花括号的统一的初始化语法,要么省略掉任何括号。
对于自由分配空间上的对象分配,缺省构造函数可以使用如下:
auto smartCellp{ make_unique<SpreadsheetCell>() };// Or with a raw pointer (not recommended)SpreadsheetCell* myCellp{ new SpreadsheetCell{ } };// Or// SpreadsheetCell* myCellp{ new SpreadsheetCell };// Or// SpreadsheetCell* myCellp{ new SpreadsheetCell() };// ... use myCellpdelete myCellp; myCellp = nullptr;
5.3、编译器生成的缺省构造函数
我们最早看到的SpreadsheetCell类的定义看起来像这样:
export class SpreadsheetCell
{
public:void setValue(double value);double getValue() const;private:double m_value;
};
该定义中并没有声明缺省的构造函数,但下面的代码仍然跑得好好的:
SpreadsheetCell myCell;
myCell.setValue(6);
下面的定义除了加上了一个显式的接受double的构造函数之外与前面的定义一样,它依然没有显式地声明缺省的构造函数。
export class SpreadsheetCell
{
public:SpreadsheetCell(double initialValue); // No default constructor// Remainder of the class definition omitted for brevity
};
这个定义,下面的代码编译就不会成功了:
SpreadsheetCell myCell;
myCell.setValue(6);
这是什么鬼?编译不成功的原因是,如果你不指定任何构造函数,编译器会生成一个不带有任何参数的构造函数。编译器生成的缺省构造函数调用在类的所有对象成员的缺省构造函数,但是不会初始化像int与double这样的语言原始类型。但是,它允许你生成这样的类。然而,如果你自己声明了任何构造函数,编译器就不会为你生成缺省的构造函数了。
注意:缺省构造函数与零参数构造函数一样。名词缺省构造函数并不只是指自动生成的构造函数,如果你没有自己声明任何构造函数的话。它也指没有参数的缺省的构造函数。
5.4、显式缺省的缺省构造函数
在c++11之前,如果类需要几个接受参数的显式的构造函数,也需要一个什么也不做的缺省构造函数,你必须显式地写出自己的空的如前所示的缺省构造函数。
为了避免手动书写空的缺省构造函数,c++支持显式缺省的缺省构造函数。这就允许你写出如下不必为缺省构造函数提供空的实现的类的定义了:
export class SpreadsheetCell
{
public:SpreadsheetCell() = default;SpreadsheetCell(double initialValue);SpreadsheetCell(std::string_view initialValue);// Remainder of the class definition omitted for brevity
};
SpreadsheetCell定义了两个客户化的构造函数。然而,因为显式缺省的default关键字的使用,编译器仍然生成了一个标准的编译器生成的缺省构造函数。至于是直接在类定义中,还是在实现文件中使用= default,那就是你的选择自由了。
5.5、显式删除的缺省构造函数
显式缺省的缺省构造函数的对立面也是可能的,叫做显式删除的缺省构造函数。例如,可以定义一个只有静态成员函数的类,不想写任何构造函数,也不想让编译器生成缺省构造函数。这种情况下,需要显式删除缺省构造函数。
export class MyClass
{
public:MyClass() = delete;
};
注意:如果类成员有删除的缺省构造函数,类的缺省构造函数也会自动删除。