一、构造函数通用
1.构造函数应当创建完全初始化的对象
构造函数的职责就是创建完全初始化的对象。类不应有init(初始化)成员函数,不然就是自找麻烦。
2.如果构造函数无法构造处有效对象,则应抛出异常
根据前面的规则,如果不能构造有效的对象,那就该抛出异常。没有太多可补充的东西,如果使用无效的对象,你就总得在使用之前检查对象的状态,这样非常烦琐、低效且容易出错。
二、默认构造函数
1.确保可拷贝的(值类型)类有默认构造函数
不正式地说,当类的实例缺少有意义的默认值时,该类就不需要默认构造函数。例如,“人”没有有意义的默认值,但是像“银行账户”这样的类型则有。银行账户的初始值可能为零。拥有默认的构造函数,可以使你的类型更容易使用。STL容器的许多构造函数都要求你的类型有默认构造函数,——例如,有序的关联容器(std::map)里的值。如果类的所有成员都有默认构造函数,编译器会尽可能为你的类生成默认构造函数。
2.不要定义仅初始化数据成员的默认构造函数,而应使用成员初始化器
class Widget
{
public:
Widget() = default;
explicit Widget(int w) : width(w), height(getHeight(w)) {}
Widget(int w, int h) : width(w), height(h) {}
void show() const
{
std::cout << width << height << "\n";
}
private:
int getHeight(int w) { return w * 3 / 4; }
int width{640};
int height{480};
bool frame{false};
bool visible{true};
};
当你在类中添加新成员时,你只需要在类的主体中添加初始化,而不必在所有的构造函数中添加。此外,你也不需要考虑将初始化器按正确的顺序放在构造函数中了。这样,当创建新对象时,也不可能发生对象只是部分初始化的情况了。
3.默认情况下,把单参数的构造函数声明为explicit
说的更准确一点,一个没有explicit的单参数构造函数是个转换构造函数。转换构造函数接受一个参数,并从该参数中生成该类的一个对象。
#include <iomanip>
#include <iostream>
#include <ostream>
namespace Distance
{
class MyDistance
{
public:
MyDistance(double d) : m(d) {}
friend MyDistance operator+(MyDistance const& a, MyDistance const& b)
{
return MyDistance(a.m + b.m);
}
friend std::ostream& operator<<(std::ostream& os, MyDistance const& d)
{
return os << d.m << "m";
}
private:
double m;
};
namespace Unit
{
MyDistance operator"" _km(long double d)
{
return MyDistance(d * 1000);
}
MyDistance operator"" _m(long double m)
{
return MyDistance(m);
}
MyDistance operator"" _dm(long double d)
{
return MyDistance(d / 10);
}
MyDistance operator"" _cm(long double c)
{
return MyDistance(c / 100);
}
}
}
using namespace Distance::Unit;
int main()
{
std::cout << std::setprecision(7) << "\n";
std::cout << 1.0_km + 2.0_dm + 3.0_cm << "\n";
std::cout << 4.0_km + 5.5 + 3.0_cm << "\n"; // 如果忘记写成5.5_dm,结果错误
}
转换构造函数把5.5变成了MyDistance(5.5),如果构造函数被定义为explicit,这种从double的隐式转换就不会发生了:explicit MyDistance(double d);
三、成员的初始化
1.按照成员声明的顺序定义和初始化成员变量
2.在使用常量来初始化时,优先选择类内初始化器,而不是构造函数的成员初始化。
3.在构造函数里优先使用初始化而不是赋值。
class Good
{
std::string s1;
public:
Good(const std::string& s2) : s1{s2} {}
}
类中的构造函数直接初始化了std::string.
四、特殊构造函数
1.使用委托构造函数来表示类的所有构造函数的共同动作
一个构造函数可以把它的工作委托给同一个类的另一个构造函数。委托是C++中把所有构造函数的共同动作放到一个构造函数中的现代方式。
class Degree
{
public:
explicit Degree(int deg)
{
degree = deg % 360;
if (degree < 0)
{
degree += 360;
}
}
Degree() : Degree(0) {}
explicit Degree(double deg) :
Degree(static_cast<int>(std::ceil(deg)))
{
}
private:
int degree;
};
2.使用继承构造函数将构造函数导入不需要进一步显式初始化的派生类中
如果可以的话,在派生类中重用基类的构造函数。当派生类没有成员时,这种重用的想法很合适。如果在可重用构造函数时不重用,你就违反了DRY(不要重复自己)原则。继承的构造函数保留了它们在基类中定义的所有特性,如访问说明符,或属性explicit和constexpr.
class Rec
{
//... 数据和很多漂亮的构造函数
};
class Oper : public Rec
{
using Rec::Rec;
//...没有数据成员
//...很多漂亮的工具函数
};
struct Rec2 : public Rec
{
int x;
using Rec::Rec;
};
Rec2 r {"foo", 7};
int val = r.x; //没有初始化
使用继承构造函数时会遇到一个危险。如果你的派生类(如Rec2)有自己的成员函数int x,它们不会被初始化,除非它们有类内初始化器。