美文网首页c/c++already
chapter2 改进程序 性能: 右值引用 / move 语义

chapter2 改进程序 性能: 右值引用 / move 语义

作者: my_passion | 来源:发表于2022-06-18 17:24 被阅读0次
    1   右值引用 

    2   move 语义

    3   forward 和 完美转发 
                    
    4   emplace_back: 右值版本的插入函数
        
        比 push_back  减少 内存 拷贝 和 移动 
    
    5   无序容器 unordered_...
    
        插入元素时 不会自动排序
        
        在 不需要排序时, 不会带来额外的性能损耗

2.1 右值引用

1 左值 / 右值 / 右值引用 / 常量左值引用 / universal reference

    (1) 左值: 表达式结束后 依然存在的持久对象
    
    (2) 右值: 表达式结束后 不再存在的临时对象
    
        如何区分 ?
            不能取地址 => 右值 
            无名       => 右值 

        2 种右值 
            纯右值(prvalue, pure rvalue) 
                non-ref 返回   的 临时变量 
                运算表达式产生 的 临时变量 
                原始字面量
                lambda 表达式 
            
            将亡值(xvalue, expiring value) // C++11 新增
                将被移动 的      管理对象
                std::move           返回值
                T&&                 函数返回值
                转换为 T&& 类型的     转换函数的返回值
            
    (3) 右值引用: A&& // A 不经编译器类型推导
        
        是 1 种类型
        
        将 右值 的 lifetime 延长到 与 右值引用(类型的)变量 的 lifetime 一样长
        
        1) 作用/应用 
        
        [1] 减少了 新对象 a 的 
            1] copy ctor: from 函数返回的临时对象 getA()
            2] dtor 
    
        [2] 支持 move 语义
        
            RAII 类 
            前提: 类 有 move 语义的 memFunc 
                                        
            1]  隐含 move: 一侧为 右值 
                |
                |   对 左值 move 
                |/
            2]  显式 move: std::move()            
                    
        // === 右值引用应用1: 2 行代码:
        A a = getA();    
            3 steps
            1] 临时变量 getA() 到 a 的 copy 
            2] getA() 的 dtor 
            3] a 的 dtor: a scope 结束时 
            
        A&& a = getA();
            1] 右值引用 绑定 
            2] 被延长 lifetime 的 临时对象 getA() 的 dtor 
            
        struct A
        {
            A() {}
            A(const A& rhs) { }
            ~A() { }
        };
        
        A getA() 
        { 
            return A(); 
        }
        
        3 step3: 
        1] 临时变量 A() ctor 
        2] 临时变量 A() 到 临时变量 getA() 的 copy ctor 
        3] A() dtor 
            
        gcc 设 编译选项 -fno-elide-constructors 关闭返回值优化效果
            
    (4) 常量左值引用: const A&
        
        可接收 左值/右值/常量左值/常量右值 
        
        也可以实现 右值引用 作用1 的效果
            const A& a = getA();
        
    (5) universal reference 类型: 未定的引用类型 T&&/auto&&
    
        发生 自动类型推断(函数模板实参推断 T&& / auto&& 类型推断) 时,
            T&& / auto&& t 所绑定变量的 左右值特性未定
                绑定的变量可能是 左值或右值
            
        t               : 未定的引用
        T&& / auto&&    : 未定的引用类型
        
        ————————————————————————————
        t 的
        ————————————————————————————
        [1] 值特性     |   左值或右值 
        ————————————————————————————
        [2] 类型  |   未定的引用类型
        ————————————————————————————

        1) universal reference 类型推断 

            规则: X 类型的 右值 被推断为 X
                           左值        X& 
                      |
                      |
                    看 实参/用于初始化的变量
            
            
            <=> 引用折叠: 经过类型推导的 T&&/auto&& 相比 右值引用 && 会发生 类型变化  
            
                右值引用 + 右值引用 = 右值引用 
                else, = 左值引用
            
            Note: 引用折叠 的 规则描述 不如 universal reference 类型推断 规则好用好记
                  与 decltype 推断规则 不同 
                  
            ——————————————————————————————————————————————————————
            X x;
                                            t       T       T&&
                                            
            f(T&& t)    
            
            左值初始化       f(x)            X&      X&      X&
                            auto t = x;     X&      X&      X&
                            
            右值初始化       f( X{} )        X       X       X&&
                            auto t = X{};   X       X       X&&
            ——————————————————————————————————————————————————————
    
            int&& x = 0;
            auto&& y = x; // x 为 int&& 型 左值 => y 是1个/类型为 int& => auto 为 int&
            
            int a, b;
            decltype(a)&& r = std::move(b); // not universal reference
    
        2) 像但不是 universal reference 的 case 
            
            // 3种 右值引用
            
            template<typename T>
            class A
            {
            public:
                A(A&& rhs);
            };
        
            void f(std::vector<T>&& vec);
            
            template <typename T>
            void f(const T&& t);  
            

2.2 move 语义: 右值引用 应用2

含 ptr 的 class

    默认 ctor 是 浅拷贝: 只 copy ptr, 不 copy 所指 memory
        
        A& A::A(const A& rhs);
        A& A::operator=(const A& rhs);
        
    问题: 若发生 copy ctor, 则 第2个 obj dtor 时, ptr 重复 delete & 指针悬挂  

    解决: 深拷贝 copy ctor
    
    问题: copy ctor 可能不必要 
          
        临时对象 A() 到 getA(), getA() 到 a 共 2 次 copy ctor 不必要 
        
    解决: 
        move ctor/assignment: 资源 ownership 转交
        
        A& A::A(A&& rhs);
        A& A::operator=(A&& rhs);
    
    Note: 提供 move ctor/assignment 的同时, 也提供 copy ctor/assignment, 以 防止 move 不成功时, 还能 copy 
    
2 个例子 
    
(1) 含 ptr 的 class

    // 浅 copy 
    class A
    {
    private:
        int* ptr;
    public:
        A() : ptr(new int(0)) {}

        ~A() { delete ptr; }
    };

    A getA()
    {
        return A();
    }

    int main()
    {
        A a = getA();
    }

    // 深 copy 
    #include <iostream> 
    
    class A
    {
    private:
        int* ptr;
    public:
        A() : ptr(new int(0)) {}

        A(const A& rhs) 
            : ptr(new int(*rhs.ptr)) { std::cout << "copy ctor \n"; }

        ~A() { delete ptr; }
    };

    A getA()
    {
        return A();
    }

    int main()
    {
        A a = getA();
    }
    
    g++ -fno-elide-constructors t.cpp -o a
    $ ./a
    copy ctor 
    copy ctor 


    // == 
    #include <iostream>

    class A
    {
    private:
        int* ptr;
    public:
        A() : ptr(new int(0)) {}

        A(const A& rhs) 
            : ptr(new int(*rhs.ptr)) {}

        A(A&& rhs) : ptr(rhs.ptr)
        {
            std::cout << "move ctor \n";
            rhs.ptr = nullptr;
        }

        ~A() { delete ptr; }
    };

    A getA()
    {
        return A();
    }

    int main()
    {
        A a = getA();
    }

    $ g++ -fno-elide-constructors t.cpp -o a
    $ ./a
    move ctor 
    move ctor 

(2) 自定义 String 

    // === copy ctor 
    #include <iostream> 
    #include <cstring> // memcpy strlen

    class MyString
    {
    private:
        char* ptr;
        size_t len;
        void copyData(const char* p)
        {
            len = strlen(p);
            ptr = new char[len + 1];
            memcpy(ptr, p, len);
            ptr[len] = '\0';
        }
    public:
        MyString()
        {
            ptr = nullptr;
            len = 0;
        }
        MyString(const char* p)
        {
            copyData(p);
        }

        MyString(const MyString& rhs)
        {
            copyData(rhs.ptr);
        }

        MyString& operator=(const MyString& rhs)
        {
            if(this != &rhs)
                copyData(rhs.ptr);

            return *this;
        }

        ~MyString()
        {
            if (ptr)
                delete[] ptr;
        }
    };

    int main()
    {
        MyString str;
        str = MyString("hello");
    }
    
    // === move ctor & move assignment
    #include <iostream> 
    #include <cstring> // memcpy strlen

    class MyString
    {
    private:
        char* ptr;
        size_t len;
        void copyData(const char* p)
        {
            len = strlen(p);
            ptr = new char[len + 1];
            memcpy(ptr, p, len);
            ptr[len] = '\0';
        }
    public:
        MyString()
        {
            ptr = nullptr;
            len = 0;
        }
        MyString(MyString&& rhs)
        {
            ptr = rhs.ptr;
            len = rhs.len;
            rhs.ptr = nullptr;
            rhs.len = 0;
        }

        MyString(const char* p)
        {
            copyData(p);
        }

        MyString(const MyString& rhs)
        {
            copyData(rhs.ptr);
        }

        MyString& operator=(const MyString& rhs)
        {
            if(this != &rhs)
                copyData(rhs.ptr);

            return *this;
        }
        MyString& operator=(MyString&& rhs)
        {
            if (this != &rhs)
            {
                ptr = rhs.ptr;
                len = rhs.len;
                rhs.ptr = nullptr;
                rhs.len = 0;
            }
            return *this;
        }

        ~MyString()
        {
            if (ptr)
                delete[] ptr;
        }
    };

    int main()
    {
        MyString str;
        str = MyString("hello");
    }

2.3 forward & 完美转发 & 万能 function wrapper

1   std::forward <utility> 
        
    // version1 
    template< class T >
    T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
    
    // version2 
    template< class T >
    T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
        
    (1) 引入
            
        func1() 接收 右值, 转发 给 func2() 时 又变为 左值 
            |
            | 解决 
            |/
        std::forward 实现 完美转发 
            
            arg lvalue -> t/T&& 被推断为 int& => T = int&, 调 f1(int& t) 
                -> t 按 lvalue 转发 -> 调 f2(int& t)

            arg: rvalue -> t/T&& 被推断为 int&& => T = int, 调 f1(int&& t) 
                -> t 按 rvalue 转发 -> 调 f2(int&& t)
        
        // === 值 经 1次转发, 右值 变 左值
        #include <iostream>

        void f2(int& i) { std::cout << "lvalue: \n"; }
        void f2(int&& i) { std::cout << "rvalue: \n"; }

        template <typename T>
        void f1(T&& t)
        {
            f2(t);
        }

        int main()
        {
            f1(0); // lvalue:
        }
        
        // === std::forward 实现 完美转发
        #include <iostream>
        void f2(int& i) { std::cout << "lvalue: \n"; }
        void f2(int&& i) { std::cout << "rvalue: \n"; }

        template <typename T>
        void f1(T&& t)
        {
            f2(std::forward<T>(t));
        }

        int main()
        {
            int x = 0;
            f1(x); // lvalue: 
            f1(0); // rvalue:
        }
    
2   完美转发 
    
    保持 参数 t (所绑定值) 的 左右值特性, 将 参数转发 给 函数(模板)中 调用的 another 函数(模板) 
        以 左/右 值 调 f1, 到 f2 中 仍为 左/右 值 
            
    template <typename T>
    void f1(T&& t)
    {
        f2( std::forward<T>(t) ); 
    }
        
    ————————————————————————————————————————————————
    t 为(绑定到) X 类型的  左值      右值
    ————————————————————————————————————————————————
    =>  t/T                 X&          X           
    ————————————————————————————————————————————————        
    调 std::forward<T>(t)        
                        
        1] 对 T 去引用      X           X 
        
        2] 实参匹配形参   version1    version2 of std::forward
        
        3] 返回类型 T&&     X&          X&& 
    ————————————————————————————————————————————————
                                        
3   万能 function wrapper: universal reference + 完美转发 + 可变模板参数
        
    function 无论带不带 return value, 无论带不带 para             
    都可以委托该 function wrapper 执行 
    
    #include <iostream>
    #include <utility>

    template<typename F, typename... Args>
    inline auto funcWrapper(F&& f, Args&& ... args)
    -> decltype( f(std::forward<Args>(args)...) )
    {
        return f(std::forward<Args>(args)...);
    }

    void test1() { std::cout << "hello\n"; }
    int  test2(int x) { std::cout << x << std::endl; return x; }

    int main()
    {
        funcWrapper(test1);
        funcWrapper(test2, 1);
        funcWrapper([](int x) {
            std::cout << x << std::endl;
            return x;
            }, 1);
    }

2.4 emplace_back 比 push_back 减少 内存 拷贝 和 移动

1. emplace_back
        
    (1) 机制 
                            
        就地 通过参数 构造对象 => 无需 copy 或 move 内存 
            `要求` 容器元素类型 有 相应 Ctor
        
        push_back()
            1] 调 Allocator 的 construct() 函数 构造临时对象 (用 指定元素值)
            2] 将 临时对象 copy/move(C++11, 才支持 move) 进 容器: 
                看 优先匹配到 move ctor 还是 copy ctor 
                
    (2)
        vector<A> vecA;
        vecA.emplace_back(args...)
                            |
                            |/
                           args 作 实参 调 A 的 Ctor     

2.5 无序容器 unordered_...

    ————————————————————————————————————————————————————————————————
                    底层数据结构  通过 what 操作元素 
    ————————————————————————————————————————————————————————————————
    set/map             红黑树         排序
    ————————————————————————————————————————————————————————————————
    unordered_          哈希表         hash  => 效率更高 
                                        key 需提供 hash_value 函数
                                        自定义 key, 要提供 Hash 函数 和 比较函数
    ————————————————————————————————————————————————————————————————

相关文章

网友评论

    本文标题:chapter2 改进程序 性能: 右值引用 / move 语义

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