美文网首页
C++ 重/难点

C++ 重/难点

作者: my_passion | 来源:发表于2022-01-25 08:10 被阅读0次

1. 虚函数机制5大问题

(1) 虚成员函数指针 vs. 虚成员函数地址

vtbl 中偏移/数组索引 vs. 所存储的各 虚成员函数 真正地址

#include <iostream>

using uintPtr = unsigned int*; // 32位 指针类型, 表示 虚成员函数地址

class Base
{
public:
    Base()
    {
        std::cout << "虚成员函数指针: &Base::vf1 = " << &Base::vf1 << std::endl;
        Base::showVtbl(this, "Base");

        std::cout << "==================\n\n";
    }

    // vtbl: 虚成员函数地址 数组, elem 类型转换为 uintPtr => vtbl 可表示为 uintPtr*
    // vptr: 指向 vtbl => vptr 可表示为 uintPtr**
    static void showVtbl(Base* pb, const char* classType)
    {
        uintPtr** vptr = reinterpret_cast<uintPtr**>(pb);
        uintPtr* vtbl = *vptr; 
        std::cout << classType << " vtbl: " << vtbl << std::endl;

        // 硬编码, 只是 demo
        std::cout << "虚成员函数指针 与 虚成员函数地址 的 逻辑关系, 语法不能这么写" << std::endl;
        std::cout << "虚成员函数(真正)地址: pb->vptr[n = &" << classType << "::vf1] = " << *vtbl << std::endl;
        
    }

    virtual void vf1() { std::cout << "Base::vf1" << std::endl; }
};

class Derived : public Base
{
public:
    Derived()
    {
        std::cout << "虚成员函数指针: &Derived::vf1 = " << &Derived::vf1 << std::endl;

        Base::showVtbl(this, "Derived");
    }

    virtual void vf1() { std::cout << "Derived::vf1" << std::endl; }
};

int main()
{ 
    Derived d{};

    std::cout << "vf 调用: (*pb->vptr[n])(pb)" << std::endl;

    return 0;
}
虚成员函数指针 vs. 虚成员函数地址.png

(2) Ctor 为虚会怎样 ?

对比 Ctor 与 普通函数 func 的 汇编指令

    `compiler explorer`

[1] 非继承
    
    Ctor + func 均 non-virtual
        
        两者无区别

[2] 继承

    B 的 Ctor 比 A 的 Ctor 多了 3 条指令 

        前2条: 输入 隐含参数 this
        第3条: 调 A 的 Ctor

[3] func 设为 virtual

    A B 的 Ctor 都会多出 3 条指令 
        从 字面意思可猜出, 
            这是记录 vtbl 的 地址

    `A/B 的 Ctor 只会记录 A/B 的 Vtbl 的地址 (vptr)`
        
        1]  保证 `A 和 B 的 object 在 调用 vf 时 能区分 你我`
                        |
                        |   即 所谓的
                        |/
                    动态绑定`
                        |
                        |   实际 所有的 
                        |/
                    vf 绑定 早在 Ctor 中 就安排好了
                    
        2]  Ctor 执行过程中, 只能到 `Ctor 所属类` 的 vtbl 中 找 vf 
                
                1]  而不是 Ctor 的 指针相应的 对象 所属类 的 vtbl 中 找 vf 
                
                    若 Ctor 本身为 vf / Ctor 中调用 vf
                    
                        => 均 无法实现 多态 
                        
                2]  vf 调用 实现多态: 必须用 对象的 ptr 或 ref 调用 
                    
                    Ctor 执行中 对象还没产生 
                    
                3]  vf 实现多态 
                    
                    ptr -> 运行时 对象 ->  对象的确切类型 的 vtbl 的 vptr -> vtbl + 虚成员函数指针 -> vf 真正地址 -> 调用
                    pb          derived 
                    
                    若 ctor 为虚, 据 Ctor() 只能找到 Ctor 所属类对象, 无 运行时对象 这一说

总结 
    Ctor 与 普通函数 一样 
    都需要 夹带1个 隐含参数 this 指针
    Derived 的 Ctor 会 夹带调用 Base 的 Ctor
    若存在 vf, Ctor 会记录 vtbl 的地址 vptr -> 保存在 object 里

1.非继承 继承下.png func 变 virtual.png vtbl.png

2 智能指针

3 swap

右值引用创建 + 应用

为 swap 定义 类 的 移动操作

(1) 右值引用 的 创建 + 应用 (C++程序设计语言 7.6 节)    
    
    1) 2 种创建方法
    
        1> static_cast<T&&>
        
        2> std::move(x)
            move(x) 
                `并不真的 移动 x`, 
                `只是 为 x 创建 右值引用`
                        |
                        |   note:
                        |       `没 move 函数 时, std::move 默认 copy 操作`
                        |           => move(内置类型) = 本身
                        |/
            之后 `用 右值引用 初始化/赋值 左值时, 才 移动`
            
    2) 应用
    
        1> 所有 `标准库容器` 都 提供了 `移动` ctor/赋值运算符
        
            1] 用于 `插入新元素`
                insert()
                push_back()
                    都提供了 接受 `右值引用` 的 版本
                    
            2] 清空 容器
                void f(vector<int>& vec)
                {
                    vec.clear();               // 清空 v
                    swap(vec, vector<int>{} ); // 清空 v
                    v = {};                    // 清空 v
                }       
            
        2> 实参转发
            23.5.2.1 / 28.6.3 
            
        3> 交换
    
        template<typename T>
        void swap(T& a, T& b)
        {
            T tmp {a}; // 创建对象: 初始化
            a = b;     // copy
            b = tmp;   // copy
        }
        只想 在 a b tmp 间 移动数据而已, 不想 copy
            |
            |   static_cast<X&&>(x), x 类型为 X
            |       结果是 X&& 类型 的 右值引用
            |
            |   note: `为 swap` 定义 `类 的 移动操作`
            |/
        template<typename T>  
        void swap(T& a, T& b)
        {
            T tmp {static_cast<T&&>(a) }; // 创建对象: 初始化 + 显式 `移动` 左值 
            a = static_cast<T&&>(b);      // 移动
            b = static_cast<T&&>(tmp);    // 移动
        }
            | static_cast 繁琐
            |  
            | move(x) <=> static_cast<X&&>(x), x 类型为 X
            |/
        template<typename T>  
        void swap(T& a, T& b)
        {
            T tmp {move(a) };// 从 a 中 `移出` 值
            a = move(b);
            b = move(tmp);
        }
            |
            | 问题: 只能交换 左值
            |
            |   解决: 加 2个重载版本, 以 交换 `左值 与 右值`
            |/
        template<typename T>  void swap(T&& a, T& b);
        template<typename T>  void swap(T& a, T&& b);

4. 函数指针 / 函数对象 / lambda

    1) 函数指针 ( 值 类型 => `不能用 typename 限定` ) 作 模板参数
        
        template <typename Key, typename V, bool (*cmp)(const Key&, const Key&) >
        class map1;
    
    2) 函数对象 -> 是否能 (隐式)转换 为 -> 函数指针
        
        一般不能, 除非 `带 转换为函数指针` 的 `类型转换运算符` ?

    3) lambda -> 是否能 转换 为 -> 函数指针
        
        匿名(普通) lamnda: 能转换
        
        命名 lambda    : 与 函数对象 规则一样 
        
    4) lambda -> 不能 转换 为 -> 函数对象类型 
        
        => 想 用 lambda 相应的 函数对象类型 
            
            命名 lambda ( auto ) + decltype()
#include <iostream>
#include <string>

// =======1 函数指针 ( 值 类型 => 不必用 typename 限定 ) 作 模板参数 + 函数指针 作 模板实参

// 版本1: 函数指针 作 模板参数 + 函数指针 作 模板实参
template <typename Key, typename V, bool (*cmp)(const Key&, const Key&) >
class map1
{
public:
    map1() { /* ... */ }
    // ...
};

bool insensitive(const std::string& s1, const std::string& s2) { /* ... */ return true;  }

void test1()
{
    map1<std::string, int, insensitive> m;
}

// 版本2: 函数指针 别名 作 模板参数 + 函数指针 作 模板实参 <=> 版本1
template<typename T>
    using CmpPtr = bool (*)(const T&, const T&);

template <typename Key, typename V, CmpPtr<Key> > 
class map2
{
public:
    map2() { /* ... */ }

    map2(CmpPtr<Key> c) : cmpPtr{ c } { /* ... */ }
    // ...
private:
    CmpPtr<Key> cmpPtr;
};

void test2()
{
    map2<std::string, int, insensitive> m2;

    // Note: 编译报错: 第3模板参数 只能是 值(类型), 不能是 类型(类型)
    //map2<std::string, int, CmpPtr<std::string> > m21(insensitive); 
}

// =======2 函数对象 ( 类型 类型 ) 作 模板参数
template <typename Key, typename V, typename Compare = std::less<Key> >
class map3
{
public:
    map3() { /* ... */ }

    map3(Compare c) : cmp{ c } { /* ... */ } // 覆盖 默认 Ctor
    // ...
private:
    Compare cmp {};                          // 默认比较 
};

// 函数对象
class Cmp
{
public:
    bool operator()(const std::string& a, const std::string& b) { return a < b; }
};

// 函数对象: 带 转换为函数指针的 `类型转换运算符`
class Cmp2
{
public:
    bool operator()(const std::string& a, const std::string& b) { return a < b; }

    operator CmpPtr<std::string>() { return insensitive; }
};

void test3()
{
    map3<std::string, int, std::greater<std::string> > m3;

    // 版本3: 函数对象 作 模板参数 + 函数指针 作 模板实参 
    map3<std::string, int, CmpPtr<std::string> > m31{ insensitive };

    // 版本4: 函数对象 作 模板参数 + 能转换为 函数指针 的 lambda (匿名 lambda)  作 模板实参 
    map3<std::string, int, CmpPtr<std::string> > m32{ [](const std::string& a, const std::string& b) { return a < b; } };

    // 版本5: 函数对象 作 模板参数 + 能转换为 函数指针 的 函数对象 (必须提供 转换为函数指针的 类型转换运算符) 作 模板实参 
    Cmp2 cmp2;
    map3<std::string, int, CmpPtr<std::string> > m33{ cmp2 };

    // Cmp cmp;
    // Note: 编译报错: 无法从 函数对象 cmp 转换为 CmpPtr<std::string> 类型
    // map3<std::string, int, CmpPtr<std::string> > m34( cmp ); 

}

void test4()
{
    // Note: 编译报错: lambda -> 不能 转换 为 -> 函数对象类型
    // 想 用 lambda 相应的 函数对象类型 -> solu: 命名 lambda ( auto ) + decltype()
    // map3<std::string, int, Cmp > m35{ [](const std::string& a, const std::string& b) { return a < b; } };

    auto cmp = [](const std::string& a, const std::string& b) { return a < b; };
    map3<std::string, int, decltype(cmp) > m36{ cmp };
}

int main()
{
    test1();
    test2();
    test3();

    return 0;
}

5 数据抽象 和 封装

    (1) 数据抽象

        interface 与 implement 分离

    (2) 封装

        1)  hide implement detail

        2)  2种形式 
        
            class / function

    (3) vector vs. 数组
     
        1) vector
        
            抽象
            
                使用 时, 只需 考虑其 interface
            
            封装
            
                是 class template, 

        2)  数组 
        
            非 抽象 / 非 封装

6 this 是 const ptr: 编译器 隐含定义 的 non-static memFunc 的 Para

    1   必须 用 this 的情形

        需 `整体 use` 调用 non-static  memFunc 的 `obj 时

        A& A::f()
        {
            ...
            return *this;
        }


    2   this 是 const ptr

            A * const this
            
                this 本身 不可改变

        const mem fuc: 保证不 modify *this ( 的 immutable mem data )

            => 只能 return *this 的 const reference: const A&

                A& A::f()
                {
                    ...
                    return *this;
                }

                const A& A::f() const
                {
                    ...
                    return *this;
                }   

7 const memFunc

    1   memFunc1 被 memFunc2
    
        `non-const 版本 和 const 版本 都调用` 时
            
            memFunc1 `必须为 const 版本`
    
    2   基于 const 的 2种 重载
    
        [1] 基于 ptr para 是否 指向 const type, 可重载 func

        [2] 基于 mem func 是否 为 const, 可重载 mem func
            memFunc 的 const / non-const 版本 是 `重载函数`


                const memFunc 中 
                    |
                    |   this 的 type 为 const ptr to const A(classType)
                    |/
                const A* const this
                    
                        1]  this 本身 

                        2]  this 所指 obj( 的 immutable mem data) 
                            
                            均 不可改变


    3   用于 public function 的 `practiacl private function`**

        小 private function 的 意义: 连续调用

            支持  对其 caller function 的 调用 

                return 的 non-const obj 上 
                
                    接着调用 该 class 的 另一 mem func

                        a1.display(cout).set(1);

    4   const & mutable ( keyword : 可变 )

        const mem func 可 modify obj 的 mutable  mem data

            mutable mem data 不能为 const`

8 类 scope


    必须 explicitly 用 class name + scope operator:: 限定的场景

        return type 
            在 mem func name 之前 =>  不在 class scope 中

9 ctor

    1   ctor 不能为 const
        
            const / non-const obj 用同一 ctor 创建

    2   分 2 阶段 
    
        [1] 初始化 
            分配 存储空间 + 编译器 中 构造 symbol table, 以 关联 标识符 与 memory
            
        [2] 赋值  
            擦除 旧值 + 写 新值

            `无论是否 explicitly use 初始化列表, 初始化阶段 都会执行`

    3   `必须 用 初始化列表` 进行初始化 的 `2种情形`

        [2] ref type 成员 
        [3] const 成员

            ctor func body` 中 对这2种 mem 的 `赋值 不起作用`

    4   单 para ctor: para type 到 class type 的 conversion

        `explicit 位置: 只在 类内 用`, 类外 不用

10 友元 friend

    1
        友元类 
        友元函数 
            memFunc
            non-memFunc 
    
    2   friend declaration / scope`

            `class / non-mem func 设为 friend 时, 相当于 global declaration 
            
                => 不必 再 forward declaration`

    3   keyword friend 位置
    
            1) 只能放 本 class A 内`
            2) friend `不是 本 class 成员` 
            => friend 放在 本 class public / protected / private 区都一样

相关文章

  • C++ 重/难点

    1. 虚函数机制 之 5大问题 (1) 虚成员函数指针 vs. 虚成员函数地址 vtbl 中 的 偏移/数组索引 ...

  • C/C++重难点

    title: C/C++重难点date: 2020-03-03 09:59:23 0. 前言 实习生面试写代码的时...

  • JS重难点梳理

    重读JS高程,系统梳理下JS重难点JS重难点梳理之事件

  • 复习-重难点

    整体回顾前端知识点-记录不清楚的点--需回顾多次的 js基础 (以下为原理代码未看透的) #继承 #模拟实现 ca...

  • Tyger英语指南(中)

    从句(初高中重难点) 从句阅读公式:1=1+1 从句写作公式:1+1=1 从句重难点:单句和连词 从句翻译:见连词...

  • day4

    重难点:几处连读 There is ice over the river, but there are holes...

  • 税务师:财务与会计

    1、框架 2、重难点 3、历年真题

  • 税务师:财务与会计 第

    1、框架 2、重难点 3、历年真题

  • java学习重难点

    三人行必有我师,人生是需要不断学习的,在这里我们相遇就是缘分,希望各位可以看完这篇文章,也欢迎大家在下面留言讨论,...

  • Android 面试重难点

    1.Android的Framework和Android apk的打包过程 底层的Binder驱动,IPC的核心,S...

网友评论

      本文标题:C++ 重/难点

      本文链接:https://www.haomeiwen.com/subject/knmqcrtx.html