C++11之右值引用(三):使用C++11编写string类以及“异常安全”的=运算符

前面两节,说明了右值引用和它的作用。下面通过一个string类的编写,来说明右值引用的使用。

相对于C++98,主要是多了移动构造函数和移动赋值运算符

先给出一个简要的声明:

class String
{
public:
    String();
    String(const char *s); //转化语义
    String(const String &s);
    String(String &&s);
    ~String();

    String &operator=(const String &s);
    String &operator=(String &&s);

    friend ostream &operator<<(ostream &os, const String &s)
    {
        return os << s.data_;
    }
private:
    char *data_;
};

下面依次实现每个函数。

第一个是默认构造函数:

String::String()
:data_(new char[1])
{
    *data_ = 0;
    cout << "default" << endl;
}

 

然后是char*版本的构造函数:

String::String(const char *s)
:data_(new char[strlen(s) + 1])
{
    ::strcpy(data_, s);
    cout << "char *" << endl;
}

重点来了,我们提供移动构造函数:

String::String(String &&s)
:data_(s.data_)
{
    cout << "move construct" << endl;
    s.data_ = NULL; //防止释放data
}

这里最重要的一点就是要把s的data置为NULL,因为s是个右值,马上就要析构。这样就成功实现了偷取s的内容

析构函数:

String::~String()
{
    delete[] data_;
}

下面我们提供赋值运算符,这里注意一点:

一是处理自我赋值,二是要返回自身引用。

String &String::operator=(const String &s)
{
    if(this != &s)
    {
        delete[] data_;
        data_ = new char[strlen(s.data_) + 1];
        ::strcpy(data_, s.data_);
    }
    return *this;
}

String &String::operator=(String &&s)
{
    if(this != &s)
    {
        cout << "move assignment" << endl;
        delete[] data_;
        data_ = s.data_;
        s.data_ = NULL;
    }
    return *this;
}

后面的移动构造函数,依然要把s的data置为NULL。

上面两个函数看似正确,但是没有处理发生异常的情况,如果new时发生异常,但是此时原本的data已经被delete,造成错误。

如何解决?

我们提供一个swap函数:

void String::swap(String &s)
{
    std::swap(data_, s.data_);
}

一种好的处理方案是:

String &String::operator=(const String &s)
{
    String temp(s);
    swap(temp);

    return *this;
}

String &String::operator=(String &&s)
{
    String temp(s);
    swap(temp);

    return *this;
}

这样,即使生成temp时发生异常,也对自身没有影响。

注意这里没有处理自我赋值,因为自我赋值发生的情况实际比较少,而之前的代码第一行是delete,则必须处理自我赋值。

上面两个赋值运算符可以直接合为一个:

String &String::operator=(String s)
{
    swap(s);

    return *this;
}

事实上,我们在前面也提到过,除了构造函数之外,X &x和X &&类型的函数,可以合二为一为X x,采用传值。

这样,我们的最后一个实现,保证了异常安全。

 

测试代码:

int main(int argc, char const *argv[])
{
    String s("foo");
    String s2(s);
    //String s3(std::move(String("bar")));
    String s3(String("bar")); //编译器优化 直接使用char*
    cout << s3 << endl;

    s3 = s;
    s3 = String("hello");
    cout << s3 << endl;
    s3 = std::move(s2);
    cout << s3 << endl;

    return 0;
}

注意:

String s3(String("bar"));

会被编译器优化为

String s3(“bar”)

可以显式使用:

String s3(std::move(String("bar")));

 

完毕。

时间: 2024-10-04 09:42:58

C++11之右值引用(三):使用C++11编写string类以及“异常安全”的=运算符的相关文章

C++11之右值引用(二):右值引用与移动语义

上节我们提出了右值引用,可以用来区分右值,那么这有什么用处?   问题来源   我们先看一个C++中被人诟病已久的问题: 我把某文件的内容读取到vector中,用函数如何封装? 大部分人的做法是: void readFile(const string &filename, vector<string> &words) { words.clear(); //read XXXXX } 这种做法完全可行,但是代码风格谈不上美观,我们尝试着这样编写: vector<string&

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

本文摘自: http://adamcavendish.is-programmer.com/posts/38190.htm 引言 众所周知,C++11 的新特性中有一个非常重要的特性,那就是 rvalue reference,右值引用. 引入它的一个非常重要的原因是因为在 C++ 中,常常右值,通俗地讲"在等号右边的"临时变量或者临时对象,我们是无法得到它的修改权限的. 由于类的构造和析构机制,往往产生的临时变量或临时对象的拷贝构造及析构,会带来不少的时间.资源消耗. 也同样由于这样的限

C++11之右值引用:从左值右值到右值引用

C++98中规定了左值和右值的概念,但是一般程序员不需要理解的过于深入,因为对于C++98,左值和右值的划分一般用处不大,但是到了C++11,它的重要性开始显现出来. C++98标准明确规定: 左值是可以取得内存地址的变量. 非左值即为右值. 从这里可以看出,可以执行&取地址的就是左值,其他的就是右值. 这里需要明确一点,能否被赋值不是区分C++左值和右值的区别. 我们给出四个表达式: string one("one"); const string two("two&

C++11的右值引用

右值引用是C++11         引入的新特性.它解决了两类问题:实现移动语义和完美转发.本文大绝大部分内容,来自于文章:http://kuring.me/post/cpp11_right_reference/ 一:左值(lvalue)和右值(rvalue) 最初,在C中,左值和右值的定义如下:左值,是一个可以出现在赋值操作符左边或者右边的表达式:而右值是只能出现在赋值操作符右边的表达式.比如: int a = 42; int b = 43; // a and b are both l-va

c++11之右值引用和std::move

这两个特性是c++11里比较有性能提升意义的.个人认为这两个特性也体现了c++对性能提升的极限追求. 通过改写经典c++面试题mystring来体会 move不能减少临时变量的产生,但是可以减少内存的维护量 代码 //右值引用 /* 左值对象:持久存在的对象,具有名字,可以对其去地址 右值对象:临时对象,表达式结束后它就没了,不能对它取地址,它也没有名字~ 右值引用类型:引用右值的类型,用&&来表示 */ /*****************************************

c++11的右值引用、移动语义

对于c++11来说移动语义是一个重要的概念,一直以来我对这个概念都似懂非懂.最近翻翻资料感觉突然开窍,因此记下.其实搞懂之后就会发现这个概念很简单,并无什么高深的地方. 先说说右值引用.右值一般指的是表示式中的临时变量,在c++中临时变量在表达式结束后就被销毁了,之后程序就无法再引用这个变量了.但是c++11提供了一个方法,让我们可以引用这个临时变量.这个方法就是所谓的右值引用. 那么右值引用有什么用呢?避免内存copy! 不同于其它语言,在c++里变量是值语义(在JAVA.Python变量是引

C++11之右值引用、move语义

C++11中增加了一个新的类型,即右值引用(R-value reference),标记为T&& .而它的目的就是去消除不必要的深拷贝,提高性能. 概念性的东西就不多说了.直接用代码体现其优势. 实现一个MyString类: 1 class MyString { 2 public: 3 MyString():m_data(nullptr), m_len(0) {} 4 MyString(const char* p) { 5 m_len = strlen(p); 6 copy_data(p);

第二十四章 C++11特性之右值引用

右值引用,是 C++11 语言核心中最为重要的改进之一.右值引用给 C++ 带来了“Move语义”(“转移语义”),同时解决了模板编程中完美转发的问题(Perfect forwarding).右值引用使 C++ 对象有能力甄别什么是(可以看作)临时对象,对于临时对象的拷贝可以做某种特别的处理,一般来说主要是直接传递资源的所有权而不是像一般地进行拷贝,这就是所谓的 move 语义了.完美转发则是指在模板编程的时候,各层级函数参数传递时不会丢失参数的“属性”(lvalue/rvalue, const

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