指针容器库

本文详细介绍了 C++ Boost 指针容器库的使用方法与原理,包括 ptr_container 的基本概念、特点及应用场景,探讨了序列与关联指针容器的分类与功能,同时介绍了使用 assign 库与标准算法进行指针容器操作的最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《C++11/14高级编程:Boost程序库探秘》笔记

很多时候,我们需要在容器中存储指针而不是元素本身(比如元素不满足标准容器的要求,存储抽象类而不是具体类,避免值语义内存拷贝代价),但直接存储原始指针手法太初级,不安全也难于管理,替代的选择有以下几种:

  1. 使用shared_ptr,通用的解决方案,但由于容器的拷贝语义,使用存储的shared_ptr时需要进行引用计数的增减,操作效率会略微降低,而且使用迭代器操作容器内的shared_ptr也不方便。
  2. 使用内存池boost.pool。这种方法类似自行创建一个小型的垃圾回收机制,可以任意分配内存而不担心回收,创建的对象可以直接放到表尊容器,不必在容器销毁时使用delete删除指针。但这种方法缺点在于,目前C++还没有一个“泛用”的内存池,boost.pool必须为每一类对象创建一个单独的内存池。
  3. 使用Boost提供的指针容器库ptr_container,高效且安全。

基础
入门示例

ptr_container库的使用和标准容器用法非常接近。

#include <string>
#include <boost/ptr_container/ptr_vector.hpp>
using namespace boost;

int main()
{
    typedef ptr_vector<std::string> ptr_vec;    //一个容纳string的指针容器
    ptr_vec vec;

    vec.push_back(new std::string("123"));      //添加new创建的指针
    vec.push_back(new std::string("abc"));

    assert(!vec.empty());
    assert(vec.size() == 2);

    assert(vec[0] == "123");                    //使用operator[]访问元素,注意返回的是引用
    vec.back() == "def";                        //同样返回的是引用
    assert(vec[1] == "def");

    auto iter = vec.begin();
    assert(iter->length() == 3);                //迭代器直接操作指针指向的内容

    vec.clear();                                //清空容器,内存被安全释放
    return 0;
}

虽然指针容器存储的是指针,但是使用时感觉不到指针的存在,用起来就像容纳的是普通的元素,这是因为指针容器在内部多做了一次解引用操作(operator*),减少了操作原始指针可能出现的错误。
对比一下使用标准容器存储指针的用法:

std::vector<std::string*> vec;
vec.push_back(new std::string("123"));
vec.push_back(new std::string("123"));

assert(*vec[0] == "123");
*vec.back() = "def";
assert(*vec[1] == "def");

auto iter = vec.begin();
assert((*iter)->length() == 3);

第二个示例,演示了指针容器的指针所有权转移,这是指针容器的最基本特性:指针的所有权是唯一的。

#include <string>
#include <boost/ptr_container/ptr_vector.hpp>
using namespace boost;

int main()
{
    typedef ptr_vector<std::string> ptr_vec;    //一个容纳string的指针容器
    ptr_vec vec;

    std::auto_ptr<std::string> ap(new std::string("123"));
    vec.push_back(ap);                          //指针容器可以“容纳”自动指针
    assert(ap.get() == 0);                      //auto_ptr失去指针的管理权

    vec.push_back(std::auto_ptr<std::string>(new std::string("abc")));
    vec.push_back(new std::string("xyz"));
    assert(vec.size() == 3);

    ptr_vec::auto_type p = vec.release(vec.begin());    //auto_type是一个智能指针类型
    //指针容器释放一个迭代器位置指针的管理权   
    assert(vec.size() == 2);                    //指针容器不再管理已经释放的指针
    assert(p && *p == "123");                   //p正常使用

    std::string* sp = p.release();              //auto_type也可以释放指针的管理权
    assert(!p); 
    delete sp;

    ptr_vec vec2;

    vec2.transfer(vec2.end(), vec);             //可以在两个容器间转移指针的所有权
    assert(vec.empty());                        //转移后原指针容器不再管理指针
    assert(vec2.size() == 2);                   //新指针容器唯一管理指针

    return 0;

}

指针容器可以容纳智能指针auto_ptr,容纳的同时也接管了auto_ptr里原始指针的管理权,令auto_ptr不再对原始指针的生命周期负责,如果需要,指针容器也可以释放指针的管理权,成员函数release()可以释放一个指针,把它从容器中删除指针(但不删除指针指向的内容),返回一个类型为auto_type的智能指针,这个智能指针可以像原始指针一样使用。

指针容器的优缺点

优点:
- 可以直接在容器中存储动态创建的对象而无须关心它的生存周期问题,绝对不会发生内存泄漏。
- 特别支持auto_ptr对象,可以完美地配合auto_ptr工作。
- 因为直接存储原始指针,没有了智能指针的引用计数,执行效率更高,使用的内存更少
- 同样因为只存储原始指针,指针容器对元素的要求很低,可以存储不可拷贝构造、缺省构造和赋值的类型。
- 不仅可以存储具体类,也可以存储抽象类,也就是说可以是多态的容器。
- 提供异常安全保证
- 与标准容器近似的风格,用法相似
缺点:
- 存储的指针是专有的,缺少智能指针容器的灵活性
- 指针的转移、克隆等深层次概念较难理解
- 较标准容器的接口有少量但关键的变动,使用时需要小心谨慎
- 部分标准算法不能用于指针容器(但提供了等价的成员函数)

克隆分配器

虽然,指针容器不要求元素类型T是可拷贝可缺省构造可赋值,但如果容纳的对象需要创建副本,那么T就应该是可克隆的。
ptr_container库使用克隆分配器代替标准容器中的内存分配概念,用来创建等价的对象。一个可克隆的对象,应该支持下面的两个函数:

  • new_clone():创建一个与原型等价的新对象,相当于new
  • delete_clone():删除之前创建的对象,相当于delete

在头文件<boost/ptr_container/clone_allocator.hpp>中提供了这两个函数的泛型实现:

template<typename T>
inline T* new_clone(const T& r)
{
    T* res = new T(r);
    return res;
}

template<typename T>
inline void delete_clone(const T* r)
{
    checked_delete(r);
}

ptr_container库提供了两个克隆分配器:heap_clone_allocator和view_clone_allocator。

heap_clone_allocator
heap_clone_allocator是所有指针容器缺省使用的克隆分配器,它使用new_clone()/delete_clone()来分配/释放内存,因此要求元素必须满足可克隆概念。

struct heap_clone_allocator
{
    template<class U>
    static U* allocate_clone(const U& r)
    {
        return new_clone(r);
    }

    template<class U>
    static void deallocate_clone(const U* r)
    {
        delete_clone(r);
    }
};

view_clone_allocator
view_clone_allocator是一个”虚拟“的克隆分配器,它并不真正管理内存,而是提供一个只读的”指针视图“。

struct view_clone_allocator
{
    template< class U >
    static U* allocate_clone(const U& r)
    {
        return const_cast<U*>(&r);      //不做内存分配操作
    }

    template< class U >
    static void deallocate_clone(const U*)
    {
        // empty
    }
};

view_clone_allocator并不真正地克隆或删除对象,因此如果指针容器使用它作为分配器,那么就不会持有指针的管理权,变成一个安全的观察者。一般用于把指针容器转化为另一个容器的视图来使用。

指针容器的分类

两大类:序列容器和关联容器,基本上标准容器都有对应的加ptr_前缀的同名指针容器。

序列容器:

  • ptr_vector
  • ptr_deque
  • ptr_list
  • ptr_array
  • ptr_circular_buffer:类似boost::circular_buffer的循环缓冲区容器

关联指针容器:

  • ptr_set
  • ptr_multiset
  • ptr_map
  • ptr_multimap
  • ptr_unordered_set
  • ptr_unordered_map

指针容器的共通功能

reversible_ptr_container是ptr_container库中所有指针容器的基类,包含指针容器的基本操作。

template<class Config,class CloneAllocator>
class reversible_ptr_container
{
private:
    typedef Config::value_type          Ty_;    //指针容器的内部值类型
    typedef Config::void_container_type Cont;   //指针容器的内部容器类型
public:
    Cont&               base();
public:
    typedef Ty_*        value_type;         //容器值类型,即指针
    typedef Ty_*        pointer;            //容器指针类型,同值类型
    typedef Ty_&        reference;          //容器的值引用类型
    typedef const Ty_&  const_reference;    //容器的const值引用类型

    //迭代器类型
    typedef Config::iterator                    iterator;
    typedef boost::reverse_iterator<iterator>   reverse_iterator;

    //指针容器内部的智能指针类型
    typedef static_move_ptr<Ty_, Deleter>       auto_type;
};

reversible_ptr_container的主要模板参数是Config,它是一个可返回多个元数据的非标准原函数,用于元计算有关容器的各种类型,如值、迭代器等。

struct Config
{
    typedef some_define void_container_type;    //容器类型
    typedef some_define allocator_type;         //内存分配器类型
    typedef some_define value_type;             //值类型
    typedef some_define iterator;               //迭代器类型
    typedef some_define const_iterator;         //const迭代器类型
};

reversible_ptr_container支持多种形式的构造函数和赋值操作:

  • 最简单的缺省构造函数可以创建出一个不包含任何指针的空容器
  • 如果传递另一个指针容器或者一个迭代器区间,那么构造函数会使用克隆分配器克隆其中的所有元素,新容器拥有原容器里所有元素的等价副本,但原容器里的指针不受影响。
  • 如果把一个指针容器的auto_ptr传递给构造函数,那么指针容器将使用转移语义,接管原容器的所有指针。
typedef ptr_vector<std::string> ptr_vec;    //一个容纳string的指针容器

ptr_vec vec;                                //无参缺省构造函数
assert(vec.empty());                        //指针容器为空

vec.push_back(new std::string("123"));
vec.push_back(new std::string("abc"));
assert(vec.size() == 2);

ptr_vec vec2(vec);                          //从另一个容器克隆构造
assert(vec2.size() == 2);                   //两个容器各自拥有等价的对象
assert(vec.size() == 2);

auto apv = vec.release();                   //释放所有指针的所有权
assert(vec.empty());

ptr_vec vec3(apv);                          //从一个auto_ptr构造
assert(vec3.size() == 2);

ptr_vec vec4, vec5;

vec4 = vec2;                                //赋值操作,克隆
assert(vec2.size() == 2);
assert(vec4.size() == 2);

vec5 = vec3.release();                      //从一个auto_ptr赋值
assert(vec3.empty());
assert(vec5.size() == 2);

reversible_ptr_container提供了一些通用的访问元素的接口。

class reversible_ptr_container
{
public:
    iterator    insert(iterator before, Ty_* x);
    iterator    insert(iterator before, std::auto_ptr<U> x);

    iterator    erase(iterator x);
    iterator    erase(iterator first, iterator last);

    auto_type   replace(iterator where, Ty_* x);
    auto_type   replace(iterator where, std::auto_ptr<U> x);

    auto_type   release(iterator where);
    std::auto_ptr<this_type>    release();

    std::auto_ptr<this_type>    clone() const;
};

其实就是多了对auto_ptr的重载,replace()是指针容器独有的功能,用于在容器中替换当前位置的指针,然后以auto_type返回当前位置的原指针。
操作示范:

typedef ptr_vector<std::string> ptr_vec;    //一个容纳string的指针容器
ptr_vec vec;                                //无参缺省构造函数

vec.push_back(new std::string("123"));
vec.push_back(new std::string("abc"));
assert(vec.size() == 2);

auto pos = vec.insert(vec.begin(), new std::string("000"));
assert(vec.size() == 3);
assert(pos == vec.begin());

++pos;                                      //pos指向元素“123”
pos = vec.erase(pos);                       //删除操作,返回下一个元素的位置
assert(vec.size() == 2);
assert(*pos == "abc");

auto p = vec.replace(pos, new std::string("xyz"));
assert(*p == "abc");
assert(vec.size() == 2);

ptr_vec vec2;
vec2 = vec.clone();
assert(vec2.size() == 2);
assert(vec.size() == 2);

序列指针容器适配器

ptr_sequence_adapter是序列指针容器的基类,是reversible_ptr_container的子类,适配的对象有vector、list、array等。

配置元函数

ptr_sequence_adapter使用的配置元函数是sequence_config

template<class T,class VoidPtrSeq>
struct sequence_config
{
    typedef remove_nullable<T>::type    U;                      //元素值类型
    typedef VoidPtrSeq                  void_container_type;    //被适配的容器类型
    typedef VoidPtrSeq::allocator_type  allocator_type;         //内存分配器
    typedef U                           value_type;             //值类型
    typedef void_ptr_iterator<VoidPtrSeq::iterator, U>
                                        iterator;               //迭代器类型
    typedef void_ptr_iterator<VoidPtrSeq::const_iterator, const U>
                                        const_iterator;         //const迭代器类型
};

T是容器的元素类型,VoidPtrSeq是容纳void*指针的序列容器类型,元函数remove_nullable<T>可用于移除元素类型可为空指针的修饰,得到真正的元素类型U。

类摘要

因为是reversible_ptr_container的子类,所以具有reversible_ptr_container的全部功能,新增接口:

template
<class T,class VoidPtrSeq,
    class CloneAllocator = heap_clone_allocator
>
class ptr_sequence_adapter :public reversible_ptr_container<sequence_config<T, VoidPtrSeq>, CloneAllocator>
{
public:
    assign(InputIterator first, InputIterator last);    //构造与赋值
public:
    T&          front();
    T&          back();
    void        push_back(T* x);
    void        push_back(std::auto_ptr<U> x);
    auto_type   pop_back();
    void        resize(size_type size);
    void        resize(size_type size, T* to_clone);
public:
    //指针所有权转移
    //把object插入到当前容器的before位置之前,并从from中移除所有权
    void        transfer(iterator before, PtrSequence::iterator object, PtrSequence& from);
    //把[first,last)之间的元素插入到当前容器的before位置之前,并从from中移除这些元素的所有权。
    void        transfer(iterator before, PtrSequence::iterator first, PtrSequence::iterator last,PtrSequence& from);
    //把from里的所有元素插入到当前容器的before位置之前,之后from不再持有任何元素
    void        transfer(iterator before, PrtSequence& from);
    void        transfer(iterator before, value_type* from,size_type size,bool delete_from = true);
};
代码示例
typedef ptr_vector<std::string> ptr_vec;    //一个容纳string的指针容器
ptr_vec vec;                                //无参缺省构造函数

vec.push_back(new std::string("123"));
vec.push_back(new std::string("abc"));
assert(vec.front() == "123");
assert(vec.back() == "abc");

ptr_vec vec2;
vec2.assign(vec.begin(), vec.end());        //从另一个指针容器赋值
assert(vec2.size() == 2);

assert(*vec2.pop_back() == "abc");
assert(vec2.size() == 1);

vec.resize(5);                              //修改容器大小,多出的元素用缺省构造
assert(vec.back().empty());

vec.resize(10, new std::string("xyz"));     //修改容器大小,多出的元素使用克隆
assert(vec.back() == "xyz");

vec.transfer(vec.end(), vec2.begin(), vec2);
assert(vec.size() == 11);
assert(vec2.size() == 0);

vec2.transfer(vec2.end(), vec.begin(), vec.begin() + 5, vec);
assert(vec.size() == 6);
assert(vec2.size() == 5);

vec.transfer(vec.begin(), vec2);
assert(vec.size() == 11);
assert(vec2.size() == 0);

空指针处理

1.禁用空指针
如果没有特别声明,ptr_container库的所有指针容器不允许处理空指针,也就是不可能向容器插入或者从容器中取出空指针,如果向容器插入空指针,就抛出boost::bad_pointer异常。

ptr_vector<int> vec;
vec.push_back(nullptr); //抛出bad_pointer异常

2.使用空指针
但在某些时候确实需要插入空指针,ptr_container库提供了一个特别的包装类:nullable<T>。
在指针容器声明时使用nullable<T>作为元素类型而不是T即表示容器可以容纳空指针。

ptr_vector<nullable<int> > vec;
vec.push_back(nullptr);             //可插入空指针

nullable<T>对元素类型的包装不影响指针容器的接口,因为指针容器内部已经使用元函数去除了nullable<T>的包装。
由于对空指针的操作无效,所以在访问元素时必须时刻检查指针是否为空,指针容器的处理变得复杂。ptr_container库提供了一个自由函数is_null(),用来检查指针容器的迭代器是否指向一个”真正的“空指针。
而对于ptr_vector这样的支持整数索引的指针容器还有一个成员函数is_null(),同样可以检查当前位置上是否是空指针。

typedef ptr_vector<nullable<int> > ptr_null_vec;    //可容纳空指针的容器
ptr_null_vec vec;

vec.push_back(nullptr);
vec.push_back(new int(100));

assert(vec.is_null(0));             //使用成员函数检查空指针
assert(!vec.is_null(1));

for (auto i = vec.begin(); i != vec.end(); ++i)
{
    if (!boost::is_null(i))         //自由函数检查是否为空指针
    {
        std::cout << *i << " "; 
    }
}

3.空对象模式
nullable<T>在允许我们存储空指针的同时,也迫使了我们每次访问元素都要检查,一旦疏忽大意,对空指针的操作将使整个程序崩溃。
”智能空指针“——空对象模式的存在解决了这个问题。空对象模式是一个模仿了空指针的对象,它给空指针赋予了一个合理的、可接受的行为,使得代码可以一致地处理实对象和空对象,无须添加专门的条件判断语句检查空指针。

class item :boost::noncopyable      //接口类
{
public:
    virtual ~item() {}             //析构函数必须是虚的
    virtual void print() = 0;       //纯虚函数
};

class abstract_item :public item        //抽象类
{
    std::string name;
public:
    abstract_item() {};
    abstract_item(const std::string& str) :name(str) {}
    virtual ~abstract_item() {}

    virtual void print() final override
    {
        std::cout << name << std::endl;
    }
};

class television:public abstract_item
{};

class computer:public abstract_item
{};

class null_item final :public item      //空对象,final
{
    virtual void print() {}             //什么也不做的空操作
};

int main()
{
    typedef ptr_vector<item> ptr_vec;   //指针容器,不使用nullable<T>
    ptr_vec vec;

    vec.push_back(new television);
    vec.push_back(new computer);
    vec.push_back(new null_item);

    for (auto& i : vec)     //新式for遍历容器
    {
        i.print();
    }
    vec.replace(2, new computer);   //空对象可以被替换成实对象
    vec[2].print();

    return 0;
}

关联指针容器的共通功能

associative_ptr_container是关联指针容器的基类,是reversible_ptr_container的子类,由于集合和映射这两种关联容器的差距较大,它很少公开接口,大部分是保护成员。

template<class Config,class CloneAllocator>
class associative_ptr_container :public reversible_ptr_container<Config, CloneAllocator>
{
public:
    typedef Config::key_type        key_type;
    typedef Config::key_compare     key_compare;
    typedef Config::value_compare   value_compare;
public:
    key_compare                     key_comp() const;
    value_compare                   value_comp() const;
public:
    iterator                        erase(iterator before);
    size_type                       erase(const key_type& x);
    iterator                        erase(iterator first, iterator last);
};

key_type:对于集合是值类型value_type(即元素T),对于映射是键的类型
key_compare:键值比较函数对象的类型
value_compare:值的比较函数对象的类型,基本相当于key_compare


集合指针容器适配器

集合指针容器可以分为有序和无序,也可以分为不允许重复(单键)和允许重复(多键),所以它的适配器会比较复杂。

配置元函数

集合指针容器使用的配置元函数是set_config

template
<   class Key,          //集合元素类型
    class VoidPtrSet,   //被适配的集合容器类型
    bool  Ordered>      //是否有序
struct set_config
{
    typedef VoidPtrSet                  void_container_type;    //内部集合容器容器
    typedef VoidPtrSet::allocator_type  allocator_type;         //内存分配器

    typedef Key                         value_type;             //值类型
    typedef value_type                  key_type;               //键类型同值类型

    typedef mpl::eval_if_c<...>::type   value_compare;          //有序专用
    typedef value_compare               key_compare;            //有序专用

    typedef mpl::eval_if_c<...>::type   hasher;                 //无序专用
    typedef mpl::eval_if_c<...>::type   key_equal;              //无序专用

    typedef mpl::if_c<...>::type        container_type;

    typedef void_ptr_iterator<...>      iterator;
    typedef void_ptr_iterator<...>      const_iterator;
};

需要注意的是,集合指针容器没有使用nullable<T>系列元函数,因为对于集合这样的关联容器来说,容纳空指针没有意义。所以不能使用nullable<T>作为模板参数,也不能向集合指针容器插入空指针,这点和序列指针容器不同。

单键集合容器适配器

ptr_set_adapter用于适配不允许重复(单键)的集合容器,也是reversible_ptr_container的子类,具有reversible_ptr_container的全部功能,还额外增加集合容器特有的插入操作。

template
<   class Key,                  //集合元素类型
    class VoidPtrSet,           //被适配的集合容器类型
    class CloneAllocator = heap_clone_allocator,
    bool  Ordered = true>       //是否有序
class ptr_set_adapter :public
    associative_ptr_container<set_config<Key, VoidPtrSet, Ordered>, CloneAllocator>
{
public:
    //集合特有的插入操作
    std::pair<iterator, bool>   insert(key_type* x);
    std::pair<iterator, bool>   insert(std::auto_ptr<U> x);

public:
    bool        transfer(iterator object, ptr_set_adapter& from);
    size_type   transfer(iterator first, iterator last, ptr_set_adapter& from);
    size_type   transfer(PtrSetAdapter& from);
};

insert()在插入元素时会返回一个std::pair,除了表明新位置的迭代器外还有一个bool值,用来表示插入操作是否成功,因为单键集合不允许重复的值,如果值已经存在则插入失败。


映射指针容器适配器

映射指针容器同样可以分为有序和无序,也可以分为不允许重复(单键)和允许重复(多键)。

配置元函数

映射指针容器使用的配置元函数是map_config

template
<   class T,                    //映射容器的value类型
    class VoidPtrMap,           //被适配的映射容器类型
    bool  Ordered = true>
struct map_config
{
    typedef remove_nullable<T>::type    U;                  //容器的值类型
    typedef VoidPtrMap                  void_container_type;//被适配的容器类型
    typedef VoidPtrMap::allocator_type  allocator_type;

    typedef mpl::eval_if_c<...>::type   value_compare;      //有序专用
    typedef mpl::eval_if_c<...>::type   key_compare;        //有序专用

    typedef mpl::eval_if_c<...>::type   hasher;             //无序专用
    typedef mpl::eval_if_c<...>::type   key_equal;          //无序专用

    typedef mpl::if_c<...>::type        container_type;
    typedef VoidPtrMap::key_type        key_type;           //键类型
    typedef U                           value_type;         //值类型

    typedef ptr_map_iterator<...>       iterator;
    typedef ptr_map_iterator<...>       const_iterator;
};

由于都是关联容器,所以map_config与set_config相同,都有三个模板参数,同样元计算了映射指针容器所需的各种类型,只是迭代器类型不是之前一直使用的void_ptr_iterator,因为映射容器容纳的元素是一个std::pair。ptr_map_iterator使用了boost::iterator_adaptor适配了标准映射容器的迭代器,返回一个模仿std::pair的ref_pair类型。
value_type使用了元函数remove_nullable<T>,这意味着可以使用值的空指针。

单键映射容器适配器

ptr_map_adapter用于适配不允许重复(单键)的映射容器。

template
<   class T,
    class VoidPtrMap,
    class CloneAllocator = heap_clone_allocator,
    bool  Ordered = true>
class ptr_map_adapter :
    public associative_ptr_container<map_config<T, VoidPtrMap, Ordered>, CloneAllocator>
{
public:
    typedef base_type::iterator         iterator;
    typedef base_type::const_iterator   const_iterator;
    typedef base_type::size_type        size_type;
    typedef base_type::key_type         key_type;
    typedef base_type::mapped_type      mapped_type;
    typedef base_type::const_reference  const_reference;
    typedef base_type::auto_type        auto_type;
    typedef VoidPtrMap::allocator_type  allocator_type;
public:
    std::pair<iterator, bool>   insert(key_type& key, mapped_type x);
    std::pair<iterator, bool>   insert(const key_type& key, std::auto_ptr<U> x);
    iterator                    insert(iterator before, key_type& key, mapped_type x);
    iterator                    insert(iterator before, const key_type& key, std::auto_ptr<U> x);
public:
    mapped_reference            at(const key_type& key);
    mapped_reference            operator[] (const key_type& key);
public:
    bool                        transfer(PtrMapAdapter::iterator object, PtrMapAdapter& from);
    size_type                   transfer(PtrMapAdapter::iterator first, PtrMapAdapter::iterator last, PtrMapAdapter& from);
    size_type                   transfer(PtrMapAdapter& from);
};

因为映射容器存储的是pair,所以ptr_map_adapter的类型定义较之前的容器有所不同,它的value_type是一个ref_pair类型,映射的左右类型分别定义为key_type和mapped_type(即T*)。
而映射容器的成员函数insert()直接使用mapped_type的形式,要求key_type必须是一个左值,const引用的形式必须使用std::auto_ptr来包装new的结果。例如:

typedef ptr_map<int, std::string> ptr_map_t;
ptr_map_t m;

int a = 1;
m.insert(a, new std::string("one"));                                //必须使用左值
m.insert(10, std::auto_ptr<std::string>(new std::string("ten")));   //auto_ptr可使用右值

使用assign库

boost.assign可以快速方便地赋值或初始化容器的库,使得向容器赋初值的操作变得简单:

std::vector<int> v1, v2;

using namespace boost::assign;
v1 += 1, 2, 3;                  //使用operator+=和operator,
push_back(v2)(1)(10), 20, 30;   //使用operator()和operator,

ptr_container库出现后,assign库也提供了对指针容器赋值和初始化的支持。

1.向容器添加元素
assign库在头文件<boost/assign/ptr_list_inserter.hpp>和<boost/assign/ptr_map_inserter.hpp>提供了以下四个向指针容器添加元素的辅助函数。

  • ptr_push_back<T>():使用push_back()在末端添加元素
  • ptr_push_front<T>():使用push_front()在前端添加元素
  • ptr_insert<T>():使用insert()添加元素
  • ptr_map_insert<T>():使用insert()添加元素

这些辅助函数都是接受一个指针容器的引用,返回一个inserter函数对象。inserter重载了operator(),最多支持五个参数(map是六个,含键值),内部采用操作符new和参数创建对象再调用指针容器的方法添加元素,如果没有参数则使用缺省构造函数,所以无须使用new,同时也避免了映射容器对键值的左值要求。
在通常情况下,辅助函数创建的对象类型可以使用元变成技术自动推导出来,但必要时,可以使用模板参数来明确指定创建的对象类型,比如容纳多态对象时。

using namespace boost::assign;

ptr_vector<int> v;
ptr_push_back(v)()(1)(2)(100);
assert(v.size() == 4);

ptr_list<std::complex<double> > lt;
ptr_push_front(lt)(1, 2)(0.618, 1.732);
ptr_push_back<std::complex<double>>(lt)(2.718, 3.14);   //指明模板参数

ptr_multimap<int, std::string> m;
ptr_map_insert(m)(1, "one")(1, "neo");

2.初始化容器元素
函数ptr_list_of<T>()可以创建一个匿名的指针容器列表generic_ptr_list<T>,在指针容器构造时直接初始化,较ptr_push_back()效率高。
ptr_list_of<T>()在使用时必须制定模板参数T,也是最多支持使用五个参数来创建对象,如果没有参数则使用T的缺省构造函数。

ptr_vector<int> v = ptr_list_of<int>(0)(1)(2);
ptr_deque<std::complex<double> > dq = ptr_list_of<std::complex<double> >(1, 2)(0.1, 0.34);
ptr_set<std::string> s;
s = ptr_list_of<std::string>()("abc")("xyz").to_container(s);

对于集合指针容器需要使用成员函数to_container()进行转换,但对于映射指针容器,assign库里没有提供ptr_map_list_of<T>(),不能对映射指针容器直接初始化。


使用算法
1.标准算法

C++标准库提供上百种算法,指针容器的迭代器屏蔽了内部的void*指针,提供了间接访问接口,所以大部分算法都可以搭配指针容器工作。

不变算法
不变算法基本都可以安全地应用指针容器,与标准容器无任何差异。

using namespace boost::assign;
ptr_deque<int> dq;
ptr_push_back(dq)(1)(2)(10)(10)(9);

std::cout << std::count(dq.begin(), dq.end(), 10);
std::cout << std::count_if(dq.begin(), dq.end(), [](int x) {return x > 8; });

std::cout << *std::min_element(dq.begin(), dq.end());
std::cout << *std::max_element(dq.begin(), dq.end());

std::cout << *std::find(dq.begin(), dq.end(), 2);

std::cout << std::accumulate(dq.begin(), dq.end(), 0);

ptr_list<int> lt(dq.begin(), dq.end());

assert(std::equal(dq.begin(), dq.end(), lt.begin()));

ptr_vector<item> vec;
std::for_each(vec.begin(), vec.end(), mem_fn(&item::print));

修改算法
部分修改算法也可以应用于指针容器。

using namespace boost::assign;
ptr_deque<int> dq;
ptr_push_back(dq)(1)(2)(10)(10)(9);

std::transform(dq.begin(), dq.end(), dq.begin(), [](int x) {return x + 3; });
assert(dq.front() == 4);

std::replace(dq.begin(), dq.end(), 13, 20);
assert(dq[2] == 20);

std::fill(dq.begin(), dq.end(), 99);
assert(dq.back() == 99);

ptr_vector<int> vec;
ptr_push_back(vec)(1)(2)(3);

std::copy(dq.begin(), boost::next(dq.begin(), 3), vec.begin());
assert(vec[1] == 99);
ptr_vector<int> vec(10);    //保留10个元素的空间
std::copy(dq.begin(),dq.end(),vec.begin()); //抛出异常

因为ptr_vector的构造函数不同于std::vector的构造函数,它仅仅是保留了空间,内部并没有真正分配保存元素,而copy算法使用的是覆盖语义,向未分配空间写入会产生错误。

变序算法和排序算法
变序算法和排序算法使用的是赋值和交换,因此元素必须是可赋值的,但由于指针容器中存储的是指针,因而效率没有内置的直接操作指针同名算法那么高效。

using namespace boost::assign;
ptr_deque<int> dq;
ptr_push_back(dq)(20)(1)(2)(10)(9)(10)(100);

//逆序算法 100 10 9 10 2 1 20
std::reverse(dq.begin(), dq.end());

//稳定排序 1 2 9 10 10 20 100
std::stable_sort(dq.begin(), dq.end());

//删除重复元素,搭配erase 1 2 9 10 20 100
dq.erase(std::unique(dq.begin(), dq.end(), dq.end()));

//删除元素,搭配erase 1 2 9 10 100
dq.erase(std::remove(dq.begin(), dq.end(), 20), dq.end());

//随机打乱数据
std::random_shuffle(dq.begin(), dq.end());

//对前3个位置进行部分排序
std::partial_sort(dq.begin(), boost::next(dq.begin(), 3), dq.end());

已序区间算法
不涉及元素的变动,已序区间算法就是不变算法,完全可以应用于指针容器:

using namespace boost::assign;
ptr_deque<int> dq;
ptr_push_back(dq)(1)(2)(9)(10)(20);

assert(std::binary_search(dq.begin(), dq.end(), 9));        //二分查找
std::cout << *std::lower_bound(dq.begin(), dq.end(), 3);    //下界
std::cout << *std::upper_bound(dq.begin(), dq.end(), 10);   //上界

ptr_vector<int> vec;
ptr_push_back(vec)(2)(9)(10);
assert(std::includes(dq.begin(), dq.end(), vec.begin(), vec.end()));    //子集判定

已序区间算法中的merage()、set_union()等算法因为涉及元素的变动,用于指针容器时不够方便,因此,需要搭配插入迭代器(下个环节),通常改用成员函数。

2.序列指针容器的算法

ptr_sequence_adapter为所有序列指针容器提供了下列四个内部算法:

  • sort():快速排序(使用std::sort)
  • unique() :删除相邻的重复元素
  • erase_if() :删除满足条件的元素(使用std::remove_if)
  • merge() :合并两个已序区间的元素(使用std::inplace_merge)

sort()算法有四个重载形式:

void sort();                                        //对整个容器排序
void sort(iterator first,iterator last);            //指定一个区间排序
void sort(Compare comp);                            //指定排序的准则   
void sort(iterator begin,iterator end,Compare comp);//对指定的区间使用制定准则排序

unique()算法也有四个重载形式:

void unique();                                      //移除整个容器中连续的重复元素
void unique(iterator first,iterator last);          //指定一个区间移除
void unique(Compare comp);  
void unique(iterator begin,iterator end,Compare comp);

erase_if()只有两种重载形式:

void erase_if(Pred pred);   //Pred:比较谓词
void erase_if(iterator begin,iterator end,Pred pred);
3.关联指针容器的算法

关联指针容器分为有序和无序两种,支持的算法不同,但所有关联指针容器都提供以下三个算法:

  • find(k):查找键值k,返回迭代器位置
  • count(k):计算键值k的数量
  • equal_range(k):返回一个迭代器区间,里面所有元素键值都是k

有序关联指针容器(set)额外提供下面两个算法

  • lower_bound(k):大于等于k的元素的”下界“,返回第一个满足>=k的迭代器
  • upper_bound(k):大于k的元素的”上界“,返回第一个满足>k的迭代器

对于有序关联指针容器来说,equal_range(k)相当于(lower_bound(k),upper_bound(k))。


其他
异常

ptr_container是强异常安全的,提供三个异常类(都是std::exception的子类)

  • bad_ptr_container_operation:最通用的指针容器异常,表示发生的操作错误
  • bad_index:序列指针容器使用整数索引时出错
  • bad_pointer:不允许使用空指针时使用空指针出错
间接函数对象

ptr_container中提供了两个函数对象适配器indirect_fun和void_ptr_indirect_fun,可以把一个单参或者双参函数对象适配成可以直接操作指针的函数对象。

插入迭代器

C++标准库为标准容器提供了三种插入迭代器,可以把容器适配成迭代器之后,再将其应用于算法,而ptr_container库在头文件<boost/ptr_container/ptr_inserter.hpp>里提供了等价的三种指针插入迭代器和辅助函数,位于名字空间boost::ptr_container。

  • ptr_back_inserter(cont):使用push_back()克隆指针然后插入
  • ptr_front_inserter(cont):使用push_front()克隆指针然后插入
  • ptr_inserter(cont,before):使用insert()克隆指针然后插入
使用视图分配器

之前使用所有指针容器都是heap_clone_allocator,这是缺省使用的容器,而view_clone_allocator的优势在于它不真正地管理内存,而是提供一个只读的”指针视图“,因此可以使用它创建一个”视图容器“,安全地使用另一个容器的视角去观察原容器,不会造成任何影响。

ptr_vector<int> v = ptr_list_of<int>(100)(1)(10)(2);
typedef ptr_set<int,std::less<int>,boost::view_clone_allocator> set_view_t; //定义有序集合指针容器视图
set_view_t view(v.begin(),v.end());

for(auto& i : view)
{
    std::cout << i << ",";
}
//有序集合重新整理了元素的顺序
//1,2,10,100
关于可克隆性

可克隆性是ptr_container库里一个重要的概念,相当于标准容器对元素可拷贝的要求,但它不是指针容器所必须的,因为指针容器是泛型的,如果对元素的操作不涉及克隆,就不会使用克隆分配器,也就不涉及可克隆概念。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值