[转载]如何在C++03中模拟C++11的右值引用std::move特性

本文摘自: 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;
}

模拟原理介绍

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;
}

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

[转载]如何在C++03中模拟C++11的右值引用std::move特性

时间: 2024-10-27 06:11:42

[转载]如何在C++03中模拟C++11的右值引用std::move特性的相关文章

C++11中的右值引用和move函数

新版的C++标准库出现了很多C++11的新特性,刚开始接触确实很费劲,特别是右值引用和move函数这种基于效率的考虑损失了语言的简单直接的特性,废话不多说,先看右值引用. C++中根据const和non_const,lvalue和rvalue可分为四类对象  non_const          const lvalue  非常量左值         常量左值 rvalue  非常量右值         常量左值 如果使用C++03标准,函数的参数选择const reference(常量引用),

C++11中的右值引用及move语义编程

C++0x中加入了右值引用,和move函数.右值引用出现之前我们只能用const引用来关联临时对象(右值)(造孽的VS可以用非const引用关联临时对象,请忽略VS),所以我们不能修临时对象的内容,右值引用的出现就让我们可以取得临时对象的控制权,终于可以修改临时对象了!而且书上说配合move函数,可以大大提高现有C++的效率.那么是怎样提高它的效率的呢?看段代码先! #include <iostream> #include <utility> #include <vector

[转载] C++11中的右值引用

C++11中的右值引用 May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移动语义std::move() 右值引用和右值的关系 完美转发 引用折叠推导规则 特殊模板参数推导规则 解决完美转发问题 引用 在C++98中有左值和右值的概念,不过这两个概念对于很多程序员并不关心,因为不知道这两个概念照样可以写出好程序.在C++11中对右值的概念进行了增强,我个人理解这部分内容是C++11引入的特性中最难以理解的了.该特性的引入至少可以解决C++98中的

【转载】C++ 11中的右值引用

本篇随笔为转载,原博地址如下:http://www.cnblogs.com/TianFang/archive/2013/01/26/2878356.html 右值引用的功能 首先,我并不介绍什么是右值引用,而是以一个例子里来介绍一下右值引用的功能: #include <iostream>    #include <vector>    using namespace std; class obj    {    public :        obj() { cout <&l

如何在asp.net中获取GridView隐藏列的值?

在阅读本文之前,我获取gridview某行某列的值一般做法是这样的:row.Cells[3].Text.ToString().有点傻瓜呵呵 在Asp.net 2.0中增加了一个新的数据绑定控件:GridView,其目的用来取代Asp.net1.x中的DataGrid控件.获取GridView中的某列值的方法为   protected void GridView1_RowEditing(object sender, GridViewEditEventArgs e)    {        stri

(译)C++11中的Move语义和右值引用

郑重声明:本文是笔者网上翻译原文,部分有做添加说明,所有权归原文作者! 地址:http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html C++一直致力于生成快速的程序.不幸的是,直到C++11之前,这里一直有一个降低C++程序速度的顽症:临时变量的创建.有时这些临时变量可以被编译器优化(例如返回值优化),但是这并不总是可行的,通常这会导致高昂的对象复制成本.我说的是怎么回事呢? 让我们

C++ 11 中的右值引用

C++ 11 中的右值引用 右值引用的功能 首先,我并不介绍什么是右值引用,而是以一个例子里来介绍一下右值引用的功能: #include <iostream>    #include <vector>    using namespace std; class obj    {    public :        obj() { cout << ">> create obj " << endl; }        obj(c

C++11中的右值引用

原文出处:http://kuring.me/post/cpp11_right_reference May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移动语义std::move() 右值引用和右值的关系 完美转发 引用折叠推导规则 特殊模板参数推导规则 解决完美转发问题 引用 在C++98中有左值和右值的概念,不过这两个概念对于很多程序员并不关心,因为不知道这两个概念照样可以写出好程序.在C++11中对右值的概念进行了增强,我个人理解这部分内容是C

C++ 11中的左值引用和右值引用

1.首先区分左值和右值    左值是表达式结束后依然存在的持久对象    右值是表达式结束时就不再存在的临时对象    便捷方法:对表达式取地址,如果能,则为左值,否则为右值举例:    int a = 10    int b = 20    int *pFlag = &a    vector<int> vctTemp    vctTemp.push_back(1)    string str1 = "hello"    string str2 = "wo