stl源码学习(版本2.91)--list

stl源码学习(版本2.91)--list

一,阅读list()构造函数的收获

1,默认构造函数的作用和被调用的时机

struct no{
  no(int i){}
  //no(){
  //  std::cout << "s" << std::endl;
  //}
  long data;
};

struct A{
  no n;
};

int main(){
  A a;
}

这段代码报错,提示无法构造A类的a对象,编译器会给A类提供默认构造函数,但是A类的默认构造函数去构造它成员no类的n时,发现no类没有构造函数(理由:因为自己定义了no(int)构造函数,所以编译器就不提供no类的默认构造函数了),所以就无法构造n对象,也就无法构造a对象了。

知识点:

  • 如果类没有自己提供构造函数,则编译器会提供一个默认构造函数
  • 当类A里的成员里有类成员b时,当构造A时,就会去找b的构造函数,如果类b有构造函数或者默认构造函数则构造b成功。
1.cpp: In function ‘int main()’:
1.cpp:22:5: error: use of deleted function ‘A::A()’
   A a;
     ^
1.cpp:12:8: note: ‘A::A()’ is implicitly deleted because the default definition would be ill-formed:

2,allocator和定位new的用法

  • allocator:用于开辟内存空间,但是不调用构造函数
  • 定位new:不开辟内存空间,只调用构造函数

stl_list.h源码节选

template <class T>
struct __list_node {
  typedef void* void_pointer;
  void_pointer next;
  void_pointer prev;
  T data;
};

template <class T, class Alloc = alloc>
class list {
protected:
  typedef __list_node<T> list_node;
  typedef simple_alloc<list_node, Alloc> list_node_allocator;
public:      
  typedef list_node* link_type;

protected:
  link_type node;//list唯一的成员,是end()函数的返回值
  
public:
  list() { empty_initialize(); }
protected:
  void empty_initialize() { 
    node = get_node();
    node->next = node;
    node->prev = node;
  }
protected:
  link_type get_node() { return list_node_allocator::allocate(); }
                
  link_type create_node(const T& x) {
    link_type p = get_node();
    __STL_TRY {
      construct(&p->data, x);
    }
    __STL_UNWIND(put_node(p));
    return p;
  }
  S
  iterator insert(iterator position, const T& x) {
    link_type tmp = create_node(x);
    tmp->next = position.node;
    tmp->prev = position.node->prev;
    (link_type(position.node->prev))->next = tmp;
    position.node->prev = tmp;
    return tmp;
  }

stl_alloc.h

template<class T, class Alloc>
class simple_alloc {

public:
    static T *allocate(size_t n)
                { return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
    static T *allocate(void)
                { return (T*) Alloc::allocate(sizeof (T)); }

stl_construct.h

template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
  new (p) T1(value);
}

从以上的stl list源码可以看出:

  • list的构造函数list(),只开辟了node的内存空间,并没有构造node里的data对象。理由:这个node的哨兵node不是list里保存数据的节点。
  • 但调用insert方法时,会调用create_node,这里面既开辟了节点的内存空间(通过调用get_node();)又调用了节点里data的构造方法(通过调用construct(&p->data, x);),然后在construct里使用了定位new(new (p) T1(value)😉
  • stl里开辟空间和构造对象是分开的
  • stl里使用专用的allocator类来开辟空间

二,阅读list()析构函数的收获

  • 释放节点的方法:
    • 先调用节点里元素的析构方法(pointer->~T();)
    • 在释放节点的空间(Alloc::deallocate(p, sizeof (T));)

stl_list.h源码节选

public:
  ~list() {
    clear();//释放list里存的元素的空间
    put_node(node);//释放list的唯一的node成员变量的空间
  }
  void clear();

protected:
//调用list_node_allocator的static的deallocate方法,释放空间
//list_node_allocator是simple_alloc<list_node, Alloc>
void put_node(link_type p) { list_node_allocator::deallocate(p); }

template <class T, class Alloc = alloc>
class list {
protected:
  typedef void* void_pointer;
  typedef __list_node<T> list_node;
  typedef simple_alloc<list_node, Alloc> list_node_allocator;
  
  //使用方法destroy,来调用节点里data对象的析构方法,并使用方法put_node释放这个节点的空间
  void destroy_node(link_type p) {
    destroy(&p->data);//destroy是个inline方法,定义在下面的stl_construct.h
    put_node(p);
  }
  
template <class T, class Alloc> 
void list<T, Alloc>::clear()
{
  //得到第一个节点
  link_type cur = (link_type) node->next;
  //从第一个节点开始,依次释放每个节点
  while (cur != node) {
    link_type tmp = cur;
    cur = (link_type) cur->next;
    destroy_node(tmp);
  }
  //形成环形队列,让node的前后节点都指向它自己
  node->next = node;
  node->prev = node;
}

stl_alloc.h

template<class T, class Alloc>
class simple_alloc {
  static void deallocate(T *p, size_t n)
            { if (0 != n) Alloc::deallocate(p, n * sizeof (T)); }
  static void deallocate(T *p)
            { Alloc::deallocate(p, sizeof (T)); }

stl_construct.h

template <class T>
inline void destroy(T* pointer) {
    pointer->~T();
}
三,由写erase函数发生的Segmentation fault错误,产生的反省

erase函数代码如下:

template<typename T, typename Alloc>
typename list<T, Alloc>::iterator list<T, Alloc>::erase(iterator first, iterator last){
  //for( ; first != last; ++first) erase(first);//--------①
  while(first != last) erase(first++);//------------------②
  return iterator(last);
}

template<typename T, typename Alloc>
typename list<T, Alloc>::iterator list<T, Alloc>::erase(iterator position){
  link_type n = position.node->next;
  link_type p = position.node->prev;
  p->next = n;
  n->prev = p;
  destroy_node(position.node);
  return iterator(n);
}

//下面是iterator的++i和i++的运算符重载
//iterator类里只有一个成员变量node
  //++i
  self& operator++() {
    node = node->next;//------③
    return *this;
  }
  //i++
  self operator++(int){
    self tmp = *this;
    ++*this;
    return tmp;
  }
bool operator!=(const self & x){ return node != x.node; }

1,如果打开①处的注释,就会发生Segmentation fault错误。

  • 错误原因:第一次执行erase(first)后,first里的node指向的空间已经被释放了,所以first.node指向一个不明区域,变成了一个野指针;然后执行++first,也就是类iterator的重载运算符前++方法,也就是执行到了③处,node本身就是first.node,已经是野指针了,所以node->next就导致了Segmentation fault错误。

2,注释掉①处的代码,使用②处的代码,就能够正确处理了。

  • 变正确的原因:释放first.node之前,已经让first.node指向了first.node->next了.

四,通过list的size方法,了解了iterator_traits技术

stl标准库 iterator_traits

c/c++ 学习互助QQ群:877684253

本人微信:xiaoshitou5854

posted @ 2019-11-05 08:14  小石王  阅读(530)  评论(0编辑  收藏  举报