effective c++ item35-39
item35:考虑虚函数的替代方案
NVI----Non-Virtual Interface
不使用虚函数接口,子类也可以实现按需求更改实现:
The Strategy Pattern via Function Pointers
也可以使用函数指针
或者使用stl::function
The “Classic” Strategy Pattern
item36:不要重新定义non-virtual函数
见https://blog.csdn.net/weixin_44609676/article/details/130382495
item37:不要重新定义重写函数的默认参数
可以看到,b和pb调用的是同一个虚方法,但是输出到结果却不一样。因为f是虚函数,在运行时才动态绑定需要执行的函数,但是参数却是静态绑定的,在编译期pb的参数就已经是1了。
即使把BC的默认参数都换成1,也不推荐这样做。因为会增大维护代码的成本,如果默认参数要改,全部的相关代码都要改
可以使用第35条款的NVI来解决:
class A{
private:
virtual void f(int x) const = 0; // 子类复写f方法,调用dof方法
public:
void int dof(int x = 1) const{ // 非虚,不允许复写
cout << "A" << x <<endl;
}
}
item38:通过组合建模出has-a或实现关系
has-a关系
- has-a:For example, consider a “Car” class that has a “Engine” class as a member variable. The Car class has-a Engine, which means that the Engine object is a part of the Car object, and it cannot exist without the Car. Similarly, the Car class may have other has-a relationships with classes such as “Wheels”, “Transmission”, “Seats”, etc.
- is-a:For example, consider a “Vehicle” class and a “Car” class. The Car class is a specialized version of the Vehicle class, meaning that it inherits all of the attributes and behaviors of the Vehicle class, but it also has its own unique attributes and behaviors that make it different from other types of vehicles.
实现关系
使用list来实现set,如果用public继承,就会违反is-a的关系,因为set并不是list。就可以用包含关系来实现。
item39:EBO(空基类优化)
empty base optimization
任何对象大小至少为1(c++20:使用[[no_unique_address]])
然而,基类子对象不受此约束,而且完全可以从对象布局中优化掉
B的大小为8,因为内存对齐,在A a后面补上了3个字节。
C这种现象就叫EBO。
空类不一定是真的为空,也可以是包含了typedefs、enums、static、non-virtual函数等
不应用EBO的场景
如果某个非静态数据成员的类型与一个空基类的类型相同或者由该空基类派生,那么禁用空基类优化。因为要求两个同类型的基类子对象在最终派生类型的对象表示中必须拥有不同地址。
派生类B的内存布局是:A占一个字节,成员变量a占一个字节,然后补2个字节,int占4个字节
派生类C的内存布局是:A不占内存,int占四个字节
派生类D的内存布局是:A占一个字节,空3个字节,c、int都占4个字节
在MSVC中,有且仅有最后一个空基类应用空基类优化,其余空基类均不应用空基类优化,并分配一个字节。在GCC中,无论存在多少个空基类,均应用空基类优化,不分配任何空间,空基类地址与派生类首对象地址相同。
no_unique_address注解(C++20)
如果空成员子对象使用属性[[no_unique_address]] ,那么允许像空基类一样优化掉它们。取这种成员的地址会产生可能等于同一个对象的某个其他成员的地址。
如下面的例子,b的地址和b内成员变量的地址相同。
item39:明智的使用private继承
class A{};
class B:private A{};
void func(const A& a);
....
A a;
B b;
func(a); // right
func(b); // error
private只是实现上的继承,不包括接口上的继承,不是is-a的关系。A和B已经没有关系了,只是对软件实现上有意义。
当我们需要访问基类中的protected成员,或者是要复写虚函数时,可以考虑私有继承。