Skip to main content

如何在C++03中模擬C++11的右值引用std::move特性

最後修改時間:2013.03.19 -- 13:08

引言

衆所周知,C++11的新特性中有一個非常重要的特性,那就是rvalue reference,右值引用。

引入它的一個非常重要的原因是因爲在C++中,常常右值,通俗地講"在等號右邊的"臨時變量或者臨時對象, 我們是無法得到它的修改權限的。

由於類的構造和析構機制,往往產生的臨時變量或臨時對象的拷貝構造及析構,會帶來不少的時間、資源消耗。

也同樣由於這樣的限制,有不少C++程序員依然保有一部分C風格的寫法,例如將A = factory(B, C); 之中的A,以函數引用參數的形式傳入等等。 但在C++11之後,我們可以完全保留C++的寫法, 將右值明顯指出,就可以完成"直接獲得臨時對象"的資源的權限,例如A = std::move(B); 或者 A = factory(B, C);, 這時候就"幾乎完全"省去了拷貝的過程,通過直接獲取由factory(B, C)造出的臨時對象中的資源, 達到省略拷貝的過程,最終析構的臨時對象,實際上只是一具空空的皮囊。

以下有一個簡單的右值引用的例子:(注,本文中的例子僅僅只是例子,請大家不要使用這種風格)

    /**
     * Please use g++ -std=c++0x or g++ -std=c++11 to compile.
     */
    #include <iostream>
    #include <string>
    #include <cstring>
    
    template <typename T, std::size_t Num>
    class Array
    {
    public:
        T * _M_data;
    
        Array() : 
            _M_data(new T[Num])
        {} //default constructor
    
        Array(const Array & rhs) :
            _M_data(new T[Num])
        {
            std::cout << "Copy Constructor." << std::endl;
            memcpy(_M_data, rhs._M_data, sizeof(T)*Num);
        } //copy constructor
    
        // Move constructor -- from rvalue
        Array(Array && rhs) :
            _M_data(rhs._M_data)
        {
            std::cout << "Move Constructor." << std::endl;
            rhs._M_data = nullptr;
        }//move constructor
    
        Array & operator=(const Array & rhs) {
            std::cout << "Copy Assignment." << std::endl;
            if(this == &rhs)
                return (*this);
            memcpy(_M_data, rhs._M_data, sizeof(T)*Num);
            return (*this);
        }//copy assignment
    
        // Move Assignment -- from rvalue
        Array & operator=(Array && rhs) {
            std::cout << "Move Assignment." << std::endl;
            if(this == &rhs)
                return (*this);
    
            _M_data = rhs._M_data;
            rhs._M_data = nullptr;
            return (*this);
        }//move assignment
    
        ~Array()
        {
            std::cout << "destructor." << std::endl;
            delete[] _M_data;
        }//destructor
    
        static Array factory(const T & __default_val) {
            Array __ret;
            for(auto __i = 0ul; __i < Num; ++__i)
                __ret._M_data[__i] = __default_val;
            return __ret;
        }//factor(defalt_value)
    
        void print(const std::string & __info) const {
            std::cout << __info;
            if(_M_data == nullptr) {
                std::cout << "_M_data is nullptr." << std::endl;
                return;
            }//if
    
            for(auto __i = 0ul; __i < Num; ++__i)
                std::cout << _M_data[__i] << ' ';
            std::cout << std::endl;
        }//print()
    };//class Array<T, Num>
    
    int main()
    {
        const std::size_t NUM = 10ul;
    
        Array<int, NUM> a0;
        for(auto __i = 0ul; __i < NUM; ++__i)
            a0._M_data[__i] = __i;
        a0.print("a0: ");
    
        Array<int, NUM> a1(a0);
        a0.print("a0: ");
        a1.print("a1: ");
    
        Array<int, NUM> a2(std::move(a1));
        a1.print("a1: ");
        a2.print("a2: ");
    
        Array<int, NUM> a3;
        a3.print("a3(uninitialized): ");
        a3 = a2;
        a3.print("a3: ");
        a3 = Array<int, NUM>::factory(1024);
        a3.print("a3: ");
    
        std::cout << "----------" << std::endl;
        return 0;
    }//main

模擬原理介紹

01 屏蔽普通的copy assignment

由於我們要使用C++03的特性模擬右值引用(rvalue-reference),所以最重要的就是要先獲得對臨時對象的訪問權限。

故優先考慮的是

    a3 = Array<int, NUM>::factory(1024)

的實現。

我們考慮我們平時代碼中的operator=的重載函數,一般C++03中處理以上這句代碼的, 是使用Array & opeartor=(const Array & rhs);函數的,即我們平常說的copy assign operation

同時,由於factory(1024)返回的是一個Array,當它是臨時對象時,它不能被修改, 所以不能綁定到Array &類型,而只能綁定到const Array &類型上。 如果我們要簡單屏蔽普通的copy assignment,那麼最方便的,就是直接去除Array & operator=(const Array & rhs);函數。

在去除那個函數之後,你發現你的編譯失敗了。這就對了 :)

以下爲編譯失敗的代碼:

    #include <iostream>
    #include <string>
    #include <cstring>
    
    template <typename T, std::size_t Num>
    class Array
    {
    public:
        T * _M_data;
    
        Array() : 
            _M_data(new T[Num])
        {} //default constructor
    
        Array(const Array & rhs) :
            _M_data(new T[Num])
        {
            std::cout << "Copy Constructor." << std::endl;
            memcpy(_M_data, rhs._M_data, sizeof(T)*Num);
        } //copy constructor
    
    private: // Use private to block/disable default functions generated by c++
        Array & operator=(const Array & rhs) {
            std::cout << "Copy Assignment." << std::endl;
            if(this == &rhs)
                return (*this);
            memcpy(_M_data, rhs._M_data, sizeof(T)*Num);
            return (*this);
        }//copy assignment
    public:
    
        ~Array() {
            std::cout << "destructor." << std::endl;
            delete[] _M_data;
        }//destructor
    
        static Array factory(const T & __default_val) {
            Array __ret;
            for(std::size_t __i = 0ul; __i < Num; ++__i)
                __ret._M_data[__i] = __default_val;
            return __ret;
        }//factor(defalt_value)
    
        void print(const std::string & __info) const {
            std::cout << __info;
            if(_M_data == NULL) {
                std::cout << "_M_data is nullptr." << std::endl;
                return;
            }//if
    
            for(std::size_t __i = 0ul; __i < Num; ++__i)
                std::cout << _M_data[__i] << ' ';
            std::cout << std::endl;
        }//print()
    };//class Array<T, Num>
    
    int main()
    {
        const std::size_t NUM = 10ul;
    
        Array<int, NUM> a0;
        for(std::size_t __i = 0ul; __i < NUM; ++__i)
            a0._M_data[__i] = __i;
        a0.print("a0: ");
    
        Array<int, NUM> a1(a0);
        a0.print("a0: ");
        a1.print("a1: ");
    
        Array<int, NUM> a3;
        a3.print("a3(uninitialized): ");
        a3 = Array<int, NUM>::factory(1024);
        a3.print("a3: ");
    
        std::cout << "----------" << std::endl;
        return 0;
    }//main

02 獲得臨時對象的訪問權限

由於我們無法直接對臨時對象進行更改,所以在不改變函數factory()的情況下(改了函數就沒有意義了), 我們只能將其轉換爲另一個對象類型。這時候我們就要使用conversion operator/cast operator了, 將其轉換成我們可以具有修改權限的類型 -- 例如opeartor T &()

03 封裝通用conversion類

由於我們需要02中所述的具有可修改性的T類型,這個類型的基本要求如下:

  1. 擁有被轉換類型的所有成員變量和成員函數,能夠自由支配類型中的任意資源
  2. 沒有時間和空間上的性能損耗
  3. 通用性,即不需要爲每一個類都重寫這個T類型

所以,鑑於此,我們需要使用泛型、繼承這兩個非常重要的工具。

    template <typename T>
    class rv : public T
    {
        rv() {};
        rv(const rv & rhs) {};
        ~rv() {};
        void operator=(const rv & rhs) {};
    };//class rv

至此,我們就可以通過撰寫Array & operator=(rv<Array> & rhs)函數來完成我們的move assignment了。 不過在轉換與函數調用之間還差幾小步。

04 提供move constructormove assignment接口(這裏是機理最重要的部分)

這裏要做的是,在我們的Array類內提供move constructormove assignment的接口。

00:

希望:a = factory()時能夠產生隱式轉換,自動轉換到rv<Array>類型。 便於捕捉資源,區分a = factory()a = a0之間的差別。

做法:提供conversion opeartor

    //conversion operator -- convert to "rv<Array> &"
    operator rv<Array> &()
    {
        return *(static_cast< rv<Array> * >(this));
    }//conversion operator rv<Array> &()
    
    // When the factory returns a const object
    operator const rv<array> &() const
    {
        return *(static_cast< const rv<array> * >(this));
    }//conversion operator const rv<array> &()

01:

希望:a = factory()時調用的是Array & operator=(rv<Array> & rhs)接口。

做法:將原本設定爲private的用於屏蔽a = factory()的接口Array & opeartor=(const Array & rhs)改寫爲 用於間接調用Array & operator=(rv<Array> & rhs)接口的方式。

    //Lead to the correct move assignment emulation operator
    Array & operator=(const Array & rhs) {
        this->operator=(static_cast< rv<Array> & >(
            const_cast<Array &>(rhs)));
        return (*this);
    }//operator=(const Array & rhs)

02:

希望:區分a = factory()a = a0的調用方式。分別調用模擬的move assignment和模擬的copy assignment

做法:由於a = a0既可以匹配operator=(const Array & rhs)又可以匹配operator=(Array & rhs), 所以只需要分別撰寫兩個函數就可以達到區分的目的。另外,因爲原本標準的copy assignment已經 被使用作爲move assignment函數的跳板,即上面01解決的問題,所以我們需要重寫一個用於copy assignment

    //Lead to the correct copy assignment emulation operator
    Array & operator=(Array & rhs) {
        this->operator=(static_cast< const rv<Array> & >(
            const_cast<const Array &>(rhs)));
        return (*this);
    }//operator=(Array & rhs)
    
    // Copy Assignment -- emulated.
    Array & operator=(const rv<Array> & rhs) {
        std::cout << "Copy Assignment." << std::endl;
        if(this == &rhs)
            return (*this);
        memcpy(_M_data, rhs._M_data, sizeof(T)*Num);
        return (*this);
    }//copy assignment operator=(const rv & rhs)

我簡單總結比較了一下c++11和c++03兩個之間寫法的差別:

    /**
     * Standard c++11 style.
     */
    // Move constructor -- from rvalue
    Array(Array && rhs) :
        _M_data(rhs._M_data)
    {
        std::cout << "Move Constructor." << std::endl;
        rhs._M_data = nullptr;
    }//move constructor
    
    Array & operator=(const Array & rhs) {
        std::cout << "Copy Assignment." << std::endl;
        if(this == &rhs)
            return (*this);
        memcpy(_M_data, rhs._M_data, sizeof(T)*Num);
        return (*this);
    }//copy assignment
    
    // Move Assignment -- from rvalue
    Array & operator=(Array && rhs)
    {
        std::cout << "Move Assignment." << std::endl;
        if(this == &rhs)
            return (*this);
    
        _M_data = rhs._M_data;
        rhs._M_data = nullptr;
        return (*this);
    }//move assignment

    /**
     * Emulated rvalue-style
     */
    //Lead to the correct copy assignment emulation operator
    Array & operator=(Array & rhs) {
        this->operator=(static_cast< const rv<Array> & >(
            const_cast<const Array &>(rhs)));
        return (*this);
    }//operator=(Array & rhs)
    
    //Lead to the correct move assignment emulation operator
    Array & operator=(const Array & rhs) {
        this->operator=(static_cast< rv<Array> & >(
            const_cast<Array &>(rhs)));
        return (*this);
    }//operator=(const Array & rhs)
    
    //conversion operator -- convert to "rv<Array> &"
    operator rv<Array> &() {
        return *(static_cast< rv<Array> * >(this));
    }//conversion operator rv<Array> &()
    
    // ------------------------------
    
    // Move constructor -- emulated.
    Array(rv<Array> & rhs) :
        _M_data(rhs._M_data)
    {
        std::cout << "Move Constructor." << std::endl;
        rhs._M_data = NULL;
    }//move constructor
    
    // Copy Assignment -- emulated.
    Array & operator=(const rv<Array><array> & rhs) {
        std::cout << "Copy Assignment." << std::endl;
        if(this == &rhs)
            return (*this);
        memcpy(_M_data, rhs._M_data, sizeof(T)*Num);
        return (*this);
    }//copy assignment operator=(const rv<Array> & rhs)
    
    // Move Assignment -- emulated.
    Array & operator=(rv<Array> & rhs) {
        std::cout << "Move Assignment." << std::endl;
        if(this == &rhs)
            return (*this);
        _M_data = rhs._M_data;
        rhs._M_data = NULL;
        return (*this);
    }//move assignment operator=(rv<Array> & rhs)

05 std::move的實現

在完成我們的任務之前,我們最後還需要一個函數能夠將左值轉換成右值,以替代std::move()函數。

由於我們需要這個函數能適配所有的類型,所以它依然要使用泛型~

    /**
     * @brief std::move Implementations
     */
    template <typename T>
    inline rv<T> & move_emu(T & rhs) {
        return *(static_cast< rv<T> * >(&rhs));
    }//move_emu(T &)

然而,單單使用T &作爲參數是不夠的。因爲:

  1. 如果傳入的對象本身是右值,即本身是rv類型,我們應該返回的是它本身,而不應該返回爲rv< rv >類型。
  2. 如果傳入的對象本身是const保護的,我們不應該奪取它的資源。我們應該按照std::move的標準,調用copy assignment或者copy constructor。
    • (這樣的意義常常在於寫泛型的時候使用std::move(T),我們並不知情T是什麼類型,當爲const的時候調用copy functions即可)
  3. 我們還需要對每一個基本數據類型進行模板特化,例如template <> inline int move_emu(int rhs) { return rhs; }。(這個是體力活了 :) 自己做咯~)

code:

    /**
     * @brief used for std::move(const values);
     * -- call copy construction/assignment
     */
    template <typename T>
    inline const T & move_emu(const T & rhs) {
        return rhs;
    }//move_emu(const T &)
    
    template <typename T>
    inline rv<T> & move_emu(rv<T> & rhs) {
        return rhs;
    }//move_emu(rv<T> &)

簡單的一個例子(以上Array的完整emulation版本)

    #include <iostream>
    #include <string>
    #include <cstring>
    
    /**
     * @brief r-value class implementation
     */
    template <typename T>
    class rv : public T
    {
        rv() {};
        rv(const rv & rhs) {};
        ~rv() {};
        void operator=(const rv & rhs) {};
    };//class rv<T>
    
    /**
     * @brief std::move Implementations
     */
    template <typename T>
    inline rv<T> & move_emu(T & rhs) {
        return *(static_cast< rv<T> * >(&rhs));
    }//move_emu(T &)
    
    /**
     * @brief used for std::move(const values);
     * -- call copy construction/assignment
     */
    template <typename T>
    inline const T & move_emu(const T & rhs) {
        return rhs;
    }//move_emu(const T &)
    
    template <typename T>
    inline rv<T> & move_emu(rv<T> & rhs) {
        return rhs;
    }//move_emu(rv<T> &)
    
    template <typename T, std::size_t Num>
    class Array
    {
    /**
     * Added here -- rvalue emulation helper functions
     */
    public:
        //Lead to the correct copy assignment emulation operator
        Array & operator=(Array & rhs) {
            this->operator=(static_cast< const rv<Array> & >(
                const_cast<const Array &>(rhs)));
            return (*this);
        }//operator=(Array & rhs)
    
        //Lead to the correct move assignment emulation operator
        Array & operator=(const Array & rhs) {
            this->operator=(static_cast< rv<Array> & >(
                const_cast<Array &>(rhs)));
            return (*this);
        }//operator=(const Array & rhs)
    
        //conversion operator -- convert to "rv<Array> &"
        operator rv<Array> &() {
            return *(static_cast< rv<Array> * >(this));
        }//conversion operator rv<Array> &()
    
        // When the factory returns a const object
        operator const rv<Array> &() const
        {
            return *(static_cast< const rv<Array> * >(this));
        }//conversion operator const rv<Array> &()
    public:
        T * _M_data;
    
        Array() : 
            _M_data(new T[Num])
        {} //default constructor
    
        Array(const Array & rhs) :
            _M_data(new T[Num])
        {
            std::cout << "Copy Constructor." << std::endl;
            memcpy(_M_data, rhs._M_data, sizeof(T)*Num);
        } //copy constructor
    
        /**
         * Emulated rvalue-style
         */
        // Move constructor -- emulated.
        Array(rv<Array> & rhs) :
            _M_data(rhs._M_data)
        {
            std::cout << "Move Constructor." << std::endl;
            rhs._M_data = NULL;
        }//move constructor
    
    public:
        // Copy Assignment -- emulated.
        Array & operator=(const rv<Array> & rhs) {
            std::cout << "Copy Assignment." << std::endl;
            if(this == &rhs)
                return (*this);
            memcpy(_M_data, rhs._M_data, sizeof(T)*Num);
            return (*this);
        }//copy assignment operator=(const rv<Array> & rhs)
    
        // Move Assignment -- emulated.
        Array & operator=(rv<Array> & rhs)
        {
            std::cout << "Move Assignment." << std::endl;
            if(this == &rhs)
                return (*this);
    
            _M_data = rhs._M_data;
            rhs._M_data = NULL;
            return (*this);
        }//move assignment operator=(rv<Array> & rhs)
    
        ~Array()
        {
            std::cout << "destructor." << std::endl;
            delete[] _M_data;
        }//destructor
    
        static Array factory(const T & __default_val) {
            Array __ret;
            for(std::size_t __i = 0ul; __i < Num; ++__i)
                __ret._M_data[__i] = __default_val;
            return __ret;
        }//factor(defalt_value)
    
        void print(const std::string & __info) const {
            std::cout << __info;
            if(_M_data == NULL) {
                std::cout << "_M_data is nullptr." << std::endl;
                return;
            }//if
    
            for(std::size_t __i = 0ul; __i < Num; ++__i)
                std::cout << _M_data[__i] << ' ';
            std::cout << std::endl;
        }//print()
    };//class Array<T, Num>
    
    int main()
    {
        const std::size_t NUM = 10ul;
    
        Array<int, NUM> a0;
        for(std::size_t __i = 0ul; __i < NUM; ++__i)
            a0._M_data[__i] = __i;
        a0.print("a0: ");
    
        Array<int, NUM> a1(a0);
        a0.print("a0: ");
        a1.print("a1: ");
    
        Array<int, NUM> a2(move_emu(a1));
        a1.print("a1: ");
        a2.print("a2: ");
    
        Array<int, NUM> a3;
        a3.print("a3(uninitialized): ");
        a3 = a2;
        a3.print("a3: ");
        a3 = Array<int, NUM>::factory(1024);
        a3.print("a3: ");
    
        std::cout << "----------" << std::endl;
        return 0;
    }//main

Nota Bene

上面要寫的rv和move_emu以及helper functions都是具有一定的通用性。所以我們完全可以寫在一個文件中,作爲頭文件包含進來即可:

    /**
     * @file move_emu.hpp
     */
    #pragma once
    
    template <typename T>
    class rv : public T
    {
        rv() {};
        rv(const rv & rhs) {};
        ~rv() {};
        void operator=(const rv & rhs) {};
    };
    
    template <typename T>
    inline rv<T> & move_emu(T & rhs) {
        return *(static_cast< rv<T> * >(&rhs));
    }
    
    /**
     * @brief used for std::move(const values);
     * -- call copy construction/assignment
     */
    template <typename T>
    inline const T & move_emu(const T & rhs) {
        return rhs;
    }
    
    template <typename T>
    inline rv<T> & move_emu(rv<T> & rhs) {
        return rhs;
    }
    
    #define COPYABLE_AND_MOVABLE(TYPE)\
        public:\
        TYPE& operator=(TYPE &t)\
        {  this->operator=(static_cast<const rv<TYPE> &>(const_cast<const TYPE &>(t))); return *this;}\
        public:\
        operator rv<TYPE> &() \
        {  return *static_cast< rv<TYPE>* >(this);  }\
        operator const rv<TYPE>&() const \
        {  return *static_cast<const rv<TYPE>* >(this);  }\
        private:\
        //
    
    #define COPY_ASSIGN_REF(TYPE)\
        const rv< TYPE > &\
        //
    
    #define RV_REF(TYPE)\
        rv< TYPE > &\
        //

這時候你在類中就可以直接使用#define的宏來簡化你的寫法和記憶了 :)

再舉一個例子:

    #include "move_emu.hpp"
    
    #include <iostream>
    
    class integer
    {
        COPYABLE_AND_MOVABLE(integer)
    public:
        int * value;
    
        integer() : value(new int()) {
            std::cout << "default construct!" << std::endl;
        }//default constructor
    
        integer(int __val) : value(new int(__val)) {
            std::cout << "num construct!" << std::endl;
        }//constructor(int)
    
        integer(const integer & x) : value(new int(*(x.value))) {
            std::cout << "Copy construct!" << std::endl;
        }//copy constructor
    
        integer(RV_REF(integer) x) :
            value(x.value)
        {
            std::cout << "Move construct!" << std::endl;
    
            x.value = NULL;
        }//move constructor
    
        ~integer() {
            std::cout << "Destructor!" << std::endl;
            delete value;
        }//destructor
    
        /**
         * Copy assignment -- replace const integer & x
         *      with const rv<integer> & x
         * reason -- let factory(rhs) function use operator=(integer & x)
         */
        integer & operator=(COPY_ASSIGN_REF(integer) x) {
            std::cout << "Copy assign!" << std::endl;
    
            if(this != &x) {
                delete value;
                value = new int(*(x.value));
            }//if
            return *this;
        }//copy assignment
    
        integer & operator=(RV_REF(integer) x) {
            std::cout << "Move assign!" << std::endl;
    
            if(this != &x) {
                delete value;
                value = x.value;
                x.value = NULL;
            }//if
            return *this;
        }//move assignment
    };
    
    integer factory(int i) {
        integer ret;
        *(ret.value) = (i + 1024);
        return ret;
    }//factory(i)
    
    int main()
    {
        integer x1(100);
        std::cout << "x1 = " << *x1.value << std::endl;
        std::cout << "-----------------" << std::endl;
    
        integer x2 = move_emu(x1);
        std::cout << std::boolalpha << "x1.value == NULL: "
            << (x1.value == NULL) << std::endl;
        std::cout << "x2 = " << *x2.value << std::endl;
        std::cout << "-----------------" << std::endl;
    
        integer x3(move_emu(move_emu(x2)));
        std::cout << std::boolalpha << "x2.value == NULL: "
            << (x2.value == NULL) << std::endl;
        std::cout << "x3 = " << *x3.value << std::endl;
        std::cout << "-----------------" << std::endl;
    
        // do not use move_emu(factory(1024)), failure.
        // std::move(factory(1024)) success.
        integer x4;
        x4 = factory(1024);
        std::cout << "x4 = " << *x4.value << std::endl;
        std::cout << "-----------------" << std::endl;
    
        const integer x5(200);
        std::cout << "x5 = " << *x5.value << std::endl;
        std::cout << "-----------------" << std::endl;
    
        // std::move will convert it into a copy construction
        const integer x6 = move_emu(x5);
        std::cout << std::boolalpha << "x5.value == NULL: "
            << (x5.value == NULL) << std::endl;
        std::cout << "x6 = " << *x6.value << std::endl;
        std::cout << "-----------------" << std::endl;
    
        return 0;
    }//main

Comments

Comments powered by Disqus