设为首页 加入收藏

TOP

C++ primer读书笔记第13章:拷贝控制(三)
2016-09-12 19:03:12 】 浏览:1192
Tags:primer 读书 笔记 拷贝 控制
Ptr { friend void swap(HasPtr &lhs, HasPtr &rhs); //swap函数首先必须声明为HasPtr的友元函数 ... } inline void swap(HasPtr &lhs, HasPtr &rhs) { using std::swap; swap(lhs.ps, rhs.ps); //仅交换指针 swap(lhs.pUser, rhs.pUser); }

与拷贝控制成员不同的是,swap并不是必须的,但是对于分配了资源的类来说,这是一种很重要的优化手段。

swap函数应该调用swap,而不是std::swap

  在上述代码中我们要特别注意的一点是swap函数中调用的是swap而不是std::swap。
  比如有一个类Foo,其中有HasPtr成员h,我们希望可以通过自定义的swap操作来避免拷贝:

void swap(Foo &lhs, FOo &rhs)
{
    std::swap(lhs.h, rhs.h); 
}

  如果像上述代码的写法,可以正常编译运行,但是函数中调用的swap函数是标准库中的版本,而不是自定义交换HasPtr的版本,所以上述代码没有起到优化作用。正确写法如下:

void swap(Foo &lhs, FOo &rhs)
{
    using std::swap;   //using声明不可少
    swap(lhs.h, rhs.h); 
}

  这样如果此类型存在自定义的swap版本,则会调用自定义的swap版本,否则将使用标准库的swap版本。

在赋值运算符中使用swap

  定义了swap的类通常会使用swap来定义它们的赋值操作符,这种运算符运用了一种名为拷贝并交换的技术。如下:

    //要注意此函数中形参不是const引用,而变成了值传递,这么做的目的是为了让赋值函数能处理自赋值
    HasPtr &operator=(HasPtr rhs)
    {
        swap(*this, rhs);
        return *this;
    }

  这个技术有趣之处在于他自动处理了自赋值的情况且天生是异常安全的。它通过改变左侧运算符对象之前拷贝右侧运算对象保证了自赋值的正确性。而且代码唯一可能抛出异常的是拷贝函数中的new表达式,如果真发生了异常,它也会在我们改变左侧运算对象之前发生,所以其实异常安全的。(要特别注意理解其为什么是异常安全的)
  要注意这种情况对行为像值的类适用,但是对于有引用计数的类,仔细思考其引用计数应该如何处理。(尚有疑问)

13.4 略

13.5 动态内存管理类

  有些类需要自己进行内存分配。在本节中,我们将定义一个功能类似于vector< string >的类StrVec,注意结合书本相应章节理解此类的结构:

class StrVec
{
public:
    StrVec() = default;
    //析构函数会释放分配的内存
    ~StrVec()
    {
        free();
        alloc.deallocate(first_free, capacity - first_free);
    }
    StrVec(const StrVec& s)
    {
        //newdata作为返回值指明了首元素以及超出末端的位置
        auto newdata = alloc_n_copy(s.begin(), s.end());
        elements = newdata.first;
        first_free = cap = newdata.second;
    }
    StrVec &operator=(const StrVec &s)
    {
        auto data = alloc_n_copy(s.begin(), s.end());
        free();
        elements = data.first;
        first_free = data.second;
        return *this;
    }

public:
    size_t size() const { return first_free - elements; }
    size_t capacity() const { return cap - elements; }
    string *begin() const { return elements; }
    string *end() const{ return first_free; }
    void push_back(const string &s)
    {
        chk_n_alloc();
        //当我们使用allocator分配内存时,必须记住内存是未构造的,为了使用原始内存,我们必须调用construct函数构造一个对象
        alloc.construct(first_free++, s);
    }
private:
    //确认是否需要重新分配内存
    void chk_n_alloc()
    {
        if (size() == capacity())
            reallocate();
    }
    //工具函数
    std::pair
  
    alloc_n_copy(const string *b, const string *e)
    {
        auto data = alloc.allocate(e - b);
        //返回语句中完成拷贝工作,并且返回第一个元素以及最后一个元素后一位的指针,即begin和end
        return{ data, uninitialized_copy(b, e, data) };
    }
    //销毁元素并释放内存
    void free()
    {
        if (elements)
        {
            //逆序销毁旧元素
            for (auto p = first_free; p != elements;)
                alloc.destroy(--p);
            //释放分配的内存,我们传递给deallocate的指针必须是之前某次allocate返回的指针,因此我们首先要检查elements是否为空
            alloc.deallocate(elements, cap - elements);
        }
    }
    //重新分配更多内存并且拷贝已有元素
    void reallocate()
    {
        auto newcapacity = size()  2 * size() : 1;
        auto newdata = alloc.allocate(newcapacity);
        auto dest = newdata;
        auto elem = elements;
        for (size_t i = 0; i != size(); ++i)
        {
            //注意此处重新分配内存拷贝旧成员的时候,使用标准库的move函数,可以避免分配和释放string的额外开销,从而提升效率
            alloc.construct(dest++, std::move(*elem++));
            free();
        }
        elements = newdata;
        first_free = dest;
        cap = elements + newcapacity;
    }

private:
    static std::allocator
   
     alloc; //用于分配元素的内存 string *elements = nullptr; //指向数组中首元素的指针 string *first_free = nullptr; //指向数组中第一个空闲元素的指针 string *cap = nullptr; //指向数组尾后位置的指针,为超出末端 };
   
  

13.6对象移动

  新标准的一个最主要的特

首页 上一页 1 2 3 4 5 下一页 尾页 3/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇c++转码基础(1):各种编码类型及un.. 下一篇Effective Modern C++ 条款32 对..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目