您的位置:首页 > 编程语言 > C语言/C++

二十分钟弄懂C++11 的 rvalue reference (C++ 性能剖析 (5))

2014-09-05 07:28 459 查看
C++ 11加了许多新的功能。其中对C++性能和我们设计classconstructorassignment可能产生重大影响的非rvalue reference莫属!我看了不少资料,能说清它的不多。下面我企图用简单的例子来说明,希望读者能够理解并应用这一重要的语言构造。

1.rvalue reference 是reference (即指针)

比如下面两条语句的语义完全一样:

int &&p = 3; // line 1

const int &cp = 3 // line 2

2. rvalue reference 指向临时变量

上面的line1line2的共同点是,他们都指向临时变量。所不同的是下面两句:

p = 5; // p 的内容变成了5

cp = 5; // 编译出错:cp 不能改动(常数)

3.rvalue reference可以简化moving 语义 – 提高object 拷贝性能

很好,我们现在可以通过rvalue reference修改(阴暗中的)临时变量了。那么这有什么用呢?目前C++11所宣称的最主要的应用就是所谓的“moving semantics (迁移语义)”。请看下面例子:

class SimpleString

{

char * _ptr;

public:

SimpleString(const char *p);

SimpleString(const SimpleString & another);

~SimpleString();

operator const char * () { return _ptr; }

SimpleString & operator = (const SimpleString & another);

static void Test();

private:

void GetStr(const char *p);

};

SimpleString::SimpleString(const char *p): _ptr(nullptr)

{

GetStr(p);

}

SimpleString::SimpleString(const SimpleString & another): _ptr(nullptr)

{

GetStr(another._ptr);

}

void SimpleString::GetStr(const char *p)

{

if (_ptr)

delete [] _ptr;

size_t l= ::strlen(p);

_ptr = new char[l+1];

::strcpy_s(this->_ptr, l+1, p);

}

SimpleString::~SimpleString()

{

if (_ptr)

{

delete [] _ptr;

printf("SimpleString d'tr called for \n");

}

}

SimpleString & SimpleString::operator = ( const SimpleString & another)

{

GetStr(another._ptr);

return *this;

}

namespace

{

// simple string factory

SimpleString CreateString()

{

SimpleString temp("A temp string created!");

return temp;

}

}

void SimpleString::Test()

{

SimpleString ret = CreateString();

printf("ret is: &s \n", ret);

}

上面是一个为了试验用的简单string class。 假设我们有一个函数CreateString, 返回一个创建的SimpleString 值。然后赋给接受变量ret。 这个简单的逻辑有什么问题呢?

这里就是临时变量copy constructor的问题。我们这里用了SimpleString::SimpleString(const SimpleString & another),它用GetStr来构建一个新的指针_ptr。然后将临时变量的_ptr所指内容拷贝过来。

这是常见的做法,但是很昂贵的。CreateString函数已经构建了一个有效的_ptr,为什不能拷贝指针呢?

原来,因为CreateString里的temp变量是临时变量,它在CreateString出口时将会被销毁,除非我们能获取他的referencepointer,然后将它的_ptr设为null。这是个好主意,我们再加一个函数:

void SimpleString::MoveStr(SimpleString & another)

{

if (this->_ptr)

delete this->_ptr;

this->_ptr = another._ptr;

another._ptr = nullptr;

}

然后把copy constructor 改写:

SimpleString::SimpleString(const SimpleString & another): _ptr(nullptr)

{

MoveStr(const_cast<SimpleString &>(another)); // line 100

}

这样一来,我们就只构建一次_ptr了,测试的结果也证明了这一点。

上面讲的和rvalue reference有何关系呢?

我对line 100的方案不太满意:

1) 我们改变了原来copy constructor的常规意义,现在只要你赋值与另一变量,你就失去了你自己的值。我们希望这个功能只适合于“临时变量”。

2) const_cast 不太好,不美观。

现在,我们因该悟出rvalue reference的意义了吧?

根据第二节,rvalue reference是指向临时变量的,正好是用于指向CreateString产生的临时变量。

原来,只要我们在SimpleString里加一个moving copy constructor(注意&&):

SimpleString::SimpleString(SimpleString && another): _ptr(nullptr)

{

MoveStr(another);

}

我们便无需更改SimpleString::SimpleString(const SimpleString & another)了。C++编译自动地在这一行SimpleString ret = CreateString() call 我们的moving constructor SimpleString::SimpleString(SimpleString && another), 而不是我们的copy constructor.

大家不妨试试!

总结

C++11利用rvalue reference,使我们可以方便地实现 moving constructor 语义。这对上述类似的问题(特别是std里的container用法)提供了解决C++传统的临时变量拷贝的功能隐患。

附录:修改后的代码

// header: RValueRef.h

class SimpleString

{

char * _ptr;

public:

SimpleString(const char *p);

SimpleString(const SimpleString & another);

SimpleString(SimpleString && another); // moving constructor

~SimpleString();

operator const char * () { return _ptr; }

SimpleString & operator = (const SimpleString & another);

SimpleString & SimpleString::operator = ( SimpleString && another);

static void Test();

private:

void GetStr(const char *p);

void MoveStr(SimpleString & another);

};

// C++: rvalue.cpp

#include "stdafx.h"

#include <string.h>

#include <stdlib.h>

#include <stdio.h>

#include <errno.h>

#include "RValueRef.h"

SimpleString::SimpleString(const char *p): _ptr(nullptr)

{

GetStr(p);

}

SimpleString::SimpleString(const SimpleString & another): _ptr(nullptr)

{

GetStr(another._ptr);

}

// Moving constructor helps move temp var’s _ptr to ourselves.

SimpleString::SimpleString(SimpleString && another): _ptr(nullptr)

{

MoveStr(another);

}

void SimpleString::GetStr(const char *p)

{

if (_ptr)

delete [] _ptr;

size_t l= ::strlen(p);

_ptr = new char[l+1];

::strcpy_s(this->_ptr, l+1, p);

}

SimpleString::~SimpleString()

{

if (_ptr)

{

printf("SimpleString d'tr called for '%s'\n", _ptr);

delete [] _ptr;

}

}

SimpleString & SimpleString::operator = ( const SimpleString & another)

{

GetStr(another._ptr);

return *this;

}

SimpleString & SimpleString::operator = ( SimpleString && another)

{

MoveStr(another);

return *this;

}

void SimpleString::MoveStr(SimpleString & another)

{

if (this->_ptr)

delete this->_ptr;

this->_ptr = another._ptr;

another._ptr = nullptr; // don’t forget to do this

}

namespace

{

SimpleString CreateString()

{

SimpleString temp("A temp string created!");

return temp;

}

}

void SimpleString::Test()

{

SimpleString ret = CreateString();

printf("ret is: &s \n", ret);

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: