What & How & Why

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录前一修订版
后一修订版
前一修订版
cs:programming:cpp:courses:cpp_basic_deep:chpt_13 [2024/11/20 05:12] – [using 与成员函数] codingharecs:programming:cpp:courses:cpp_basic_deep:chpt_13 [2024/11/20 13:01] (当前版本) – [using 与重写] codinghare
行 393: 行 393:
 // 错误,无法找到匹配函数 // 错误,无法找到匹配函数
 d.func(1); d.func(1);
 +</code>
 +<WRAP center round box 100%>
 +这点同样适应于构造函数。通常情况下,如果派生类中没有引入新的数据成员,那么可以使用 ''using'' 直接 “借” 基类的构造函数逻辑使用。
 +但当派生类中定义了构造函数时,派生类的初始化则会调用派生类的构造函数。两者达到的效果相同,但的路径不同:
 +  * ''using Base::Cstr'':使用 ''Base::Cstr()'' 构造
 +  * 派生类中定义了构造函数时:通过 ''Derived::Cstr()'' -> 调用 ''Base::Cstr()'' 完成派生类对象的构造
 +</WRAP>
 +==using 与重写==
 +  * ''using'' 不会改变虚函数的修饰(''using'' 的优先级低于重写)
 +  * 如果希望使用 ''using'' 实现**改变权限下的重写**:
 +    * 最好的办法是使用虚函数的特性,对**特定的重载**进行重写(带函数签名的,比如 ''func(int)'',而不是 ''func''
 +    * 也可以通过在派生类中使用函数隐藏来进行重写
 +===基类指针与容器===
 + C++ 可以通过多态(基类指针)可以(有限)实现容器存储以及访问不同类型的对象:
 +<code cpp>
 +struct Base
 +{   
 +    // 访问内容函数
 +    virtual double getValue() = 0;
 +    // 使用基类指针访问派生类时,释放堆资源必须声明虚析构
 +    virtual ~Base() = default;
 +};
 +
 +struct DerivedI: public Base
 +{
 +    DerivedI(int x):val(x) {}
 +    // 注意这里的 double,限制在这里
 +    double getValue() override { return val; };
 +    int val;
 +};
 +
 +struct DerivedD: public Base
 +{
 +    DerivedD(double x): val(x) {}
 +    double getValue() override { return val; };
 +    double val;
 +};
 +int main(int argc, char const *argv[])
 +{
 +    // 使用智能指针作为基类指针
 +    std::vector<std::shared_ptr<Base>> vec;
 +    
 +    // 使用 vec 通过 new 返回的指针,存储 Base 的不同派生对象
 +    vec.emplace_back(new DerivedI(1));
 +    vec.emplace_back(new DerivedD(3.14));
 +    
 +    for (auto &obj : vec)
 +    {
 +        std::cout << obj->getValue() << " ";
 +    }
 +    std::cout << std::endl;
 +    return 0;
 +};
 +</code>
 +<WRAP center round box 100%>
 +可以看出来这种实现是有局限性的:虚函数返回的是派生类的公共类型:''int'' 可以转换成 ''double''。如果这种公共类型不存在,
 +那么这种实现也是不可能的。
 +</WRAP>
 +===多重继承与虚继承===
 +  * 虚继承:以 ''virtual'' 的方式继承
 +<code cpp>
 +Class D1 : virtual public Base { .... };
 +</code>
 +  * 解决的问题:菱形继承带来的数据成员重复的问题,保证最终继承者的数据成员不会因为多重继承而翻倍。
 +===空基类优化===
 +==空类的大小为 1==
 +空类的大小被定义为 ''1'',是因为寻址的需求。假设存在一个该类类型的数组,则其寻址是基于起始地址 + 类大小 * 元素数量来计算的:
 +<code cpp>
 +ClassType a[2];
 +a[1] -> a[0 + 1] -> address(a[0]) + 1 * sizeof(ClassType)
 +</code>
 +这种情况下,如果空类大小为 ''0'',则会导致 ''a[0]'' 和 ''a[1]'' 的地址相同。C++ 不允许存在两个地址相同但逻辑上不同的单元。
 +==空类的问题以及传统解决方案==
 +有几个前提条件:
 +  * 成员函数不占用类的空间
 +  * 根据计算机的不同,类中元素占用空间不足字的,会进行内存对齐:比如空类 ''1'' 和 ''int'' 成员的组合,大小为 ''5'' 字节,但占用 ''8'' 字节空间
 +根据上述信息,因为这个 ''1'' 的空间占用,我们为上述组合付出的代价是空间占用翻倍。传统的解决方案是将函数放置到基类中,
 +进行继承。在这种情况下,编译器会进行空基类优化,忽略空基类的大小:
 +<code cpp>
 +struct Base { // some funcs ... }; // empty class
 + 
 +// obj = 4 bytes
 +struct Derived1 : Base
 +{
 +    int i;
 +};
 +</code>
 +== C++20 的解决方案==
 +上述解决方案的问题在于,public 继承的意义是描述 //is-a// 关系,但明显该类关系不是。C++ 20 提供了一种 ''no_unique_address'' 的类型
 +用于描述空类。被该类类型定义的空类大小为 ''0''。因此,相较于继承,我们可以将函数类直接作为数据成员放置到新类中调用,而不用付出额外的空间成本:
 +
 +<code cpp>
 +struct Empty {}; // empty class
 + 
 +struct X
 +{
 +    int i;
 +    [[no_unique_address]] Empty e;
 +};
 </code> </code>