程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> [轉載]如何在C++03中模擬C++11的右值引用std::move特性

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

編輯:C++入門知識

本文摘自: http://adamcavendish.is-programmer.com/posts/38190.htm

引言

眾所周知,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; } for(auto __i = 0ul; __i < Num; ++__i) std::cout << _M_data[__i] << ' '; std::cout << std::endl; } }; 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; } View Code

 

模擬原理介紹

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; } for (std::size_t __i = 0ul; __i < Num; ++__i) std::cout << _M_data[__i] << ' '; std::cout << std::endl; } }; 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; } View Code

 

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

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

03. 封裝通用conversion類

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


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


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

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

 

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

04. 提供 move constructor 和 move assignment 接口(這裡是機理最重要的部分)

這裡要做的是,在我們的 Array 類內提供 move constructor 和 move assignment 的接口。

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

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

 

B:
希望: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
// operator =(const Array & rhs)
Array & operator =(const Array & rhs) {
    this->operator =(static_cast< rv<Array> & >(
        const_cast< Array & >(rhs)));
    return (*this);
}

 

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

// Lead to the correct copy assignment emulation operator
// operator =(Array & rhs)
Array & operator =(Array & rhs) {
    this->operator =(static_cast<const rv<Array> &>(
        const_cast<const Array &>(rhs)));
    return (*this);
}
// Copy Assignment -- emulated. // copy assignment operator =(const rv & rhs) 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); }

 

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

C++11 的實現:

/**
 * 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

C++03 的實現:

/**
 * 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);
}  // operato r=(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><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

 

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 & 作為參數是不夠的。因為:


a. 如果傳入的對象本身是右值,即本身是 rv<T> 類型,我們應該返回的是它本身,而不應該返回為 rv< rv<T> > 類型。
b. 如果傳入的對象本身是 cons t保護的,我們不應該奪取它的資源。我們應該按照 std::move 的標准,調用 copy assignment 或者 copy constructor 。
   (這樣的意義常常在於寫泛型的時候使用 std::move(T) ,我們並不知情 T 是什麼類型,當為 const 的時候調用 copy functions 即可)
c. 我們還需要對每一個基本數據類型進行模板特化,例如 template <> inline int move_emu(int rhs) { return 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;
}  // move_emu(const T &)
 
template <typename T>
inline rv<T> & move_emu(rv<T> & rhs) {
    return rhs;
}  // move_emu(rv<T> &)

 

上面要寫的 rv<T> 和 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;
 
// default constructor integer() : value(new int()) { std::cout << "default construct!" << std::endl; }
// constructor(int) integer(int __val) : value(new int(__val)) { std::cout << "num construct!" << std::endl; }
// copy constructor integer(const integer & x) : value(new int(*(x.value))) { std::cout << "Copy construct!" << std::endl; }
// move constructor integer(RV_REF(integer) x) : value(x.value) { std::cout << "Move construct!" << std::endl; x.value = NULL; }
// destructor ~integer() { std::cout << "Destructor!" << std::endl; delete value; } /** * Copy assignment -- replace const integer & x * with const rv<integer> & x * reason -- let factory(rhs) function use operator=(integer & x) */
// copy assignment integer & operator =(COPY_ASSIGN_REF(integer) x) { std::cout << "Copy assign!" << std::endl; if(this != &x) { delete value; value = new int(*(x.value)); } return *this; }
// move assignment integer & operator =(RV_REF(integer) x) { std::cout << "Move assign!" << std::endl; if (this != &x) { delete value; value = x.value; x.value = NULL; } return *this; } }; integer factory(int i) { integer ret; *(ret.value) = (i + 1024); return ret; } 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; }

 

參考:

Boost.Move

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved