STL源码剖析(1) - 空间配置器与内存操作详解

news2024/11/26 23:28:27

文章首发于:My Blog 欢迎大佬们前来逛逛

1. SGI空间配置器

SGI STL的空间配置器是 alloc而非allocator,并且不接受任何参数

vector<int,std::alloc> vec

我们通常使用缺省的空间配置器

template <typename T,typename Alloc=alloc>//指定其默认空间配置器为 alloc
class vector
{
	...
}

1.1 std::allocator

SGI的 allocator空间配置器 只是对 new 和delete 做了一层包装,效率及其不佳,不建议使用它

template <class T>
inline T* allocate(ptrdiff_t size, T*) {
    set_new_handler(0);
    T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
    if (tmp == 0) {
	    cerr << "out of memory" << endl; 
	    exit(1);
    }
    return tmp;
}


template <class T>
inline void deallocate(T* buffer) {
    ::operator delete(buffer);
}

allocate:包装了 new

deallocate:包装了 delete

因此 SGI的 allocator 是基层内存配置释放的行为,只有包装,没有效率的优势


1.2 std::alloc

SGI 重要的空间配置器:std::alloc

我们常写的c++语法:

class foo{};
class* obj=new class();
delete obj

使用new创建对象与使用delete销毁对象。

使用new时的完整操作:

  1. 首先调用 ::operator new配置内存

  1. 然后调用 构造函数 初始化对象。

同理delete的完整操作:

  1. 首先调用 析构函数 销毁对象。

  1. 然后调用 ::operator delete 释放内存

因此alloc把new和delete 以及他们各自的两部分分离开

  • alloc:allocate:配置内存

  • alloc:deallocate:释放内存

  • ::construct:构造对象

  • ::destroy:销毁对象

他们分别存在于这两个头文件中

#include <stl_alloc.h> //对象的配置与释放
#include <stl_construct>//对象的构造与析构

1.3 对象构造与析构:construct和destroy

来看 < stl_construct> 头文件中:

#include <new.h>  // 引入 new 运算符

最基本的两大函数:

template <class T>
inline void destroy(T* pointer) {
    pointer->~T();      // 析构
}

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

destroy直接负责某个对象的析构,即 ~T() 操作。

construct直接负责某个对象的创建,但是请注意这个new 是一个 placement new操作

形如: new (ptr) T(value) 是一个 placement new操作
括号里的参数ptr是一个指针,它指向一个内存缓冲器,placement new将在这个缓冲器上分配一个对象。
Placement new的返回值是这个被构造对象的地址(比如括号中的传递参数)。
placement new主要适用于:在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定的;长时间运行而不被打断的程序;以及执行一个垃圾收集器 (garbage collector)。
比如:
int n=2;
new (&n) int(10);
std::cout<<n; // n = 10

其中这两个函数还具有其他的版本

//接受两个迭代器,并设法找出元素的类型 
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
  __destroy(first, last, value_type(first));
}

destroy的第二个版本:接受两个迭代器,然后 value_type会自动推导出 此迭代器的所指的元素的类型

value_type 属于__type_traits的成员,会自动推导出迭代器所指元素的类型,在往后的 第二章 我们会学到

然后根据推导出的第三个参数的类型选择 适当的 __destroy函数。

template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
//    trivial_destructor 是__type_traits<T>::has_trivial_destructor的别名
  typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
  __destroy_aux(first, last, trivial_destructor());
}

思考一下:destroy有两个版本,第一个版本是一个对象的析构;第二个版本是 [first,last]范围 的析构

对于整个范围的析构:如果我们不知道这个范围有多大,则无论它是什么类型,我们都需要对范围的每一个元素执行析构函数,对于自定义类型:student,xxx等 具有我们显式定义的析构函数,我们必须这样做。

但是对于 int,char,long等内置类型,我们有必要对他们每次都调用析构函数(系统自带的)???

如果我们对 这些内置类型 进行一次又一次的析构,则是效率的巨大牺牲,因此有没有一种办法可以推断出即将析构的是内置类型还是自定义类型

  • 内置类型 _true_type类型 :直接什么也不做即可。

  • 非内置类型 _false_type 类型:执行每个元素的析构函数。

因此上面的destroy执行的就是这样的操作

对于判断它是什么类型的 value_type 的实现方法,我们在 迭代器一节 会给出方法。


如果是 内置类型 即**_true_type**类型:直接跳过即可

//无需析构
template <class ForwardIterator> 
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

如果是 非内置类型 即**_false_type** 类型:执行析构函数

//需要析构destory
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
  for ( ; first < last; ++first)
    destroy(&*first);
}

在false_type版本中执行的这个destroy 就是destroy的第一个版本,即直接调用

pointer->~T();      // 析构

1.4 空间配置与释放:allocate和deallocate

C++的内存配置与释放:new和delete

这两个函数相当于C的malloc和free。

因此SGI的空间配置与释放就是依据 malloc和free进行的

SGI 的双层级配置器:

  1. 第一级配置器用于 配置大小超过 128 字节的空间。

  1. 第二级配置器用于 配置大小小于 128 字节的空间。

第一级配置器:__malloc_alloc_template

__malloc_alloc_tetemplate <int inst>     //inst完全没用到
class __malloc_alloc_template 
{
    ...
}

第二级配置器:__default_alloc_template

template <bool threads, int inst>
class __default_alloc_template 
{
	...
}

无论是第一级还是第二级,SGI提供了一个接口,使得配置器的接口得以抽象

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)); }
    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)); }
};

注意:这个接口非常重要!

成员函数allocate和deallocate是可以选择调用 第一级还是第二级配置器 的实现方法的。

几乎所有的SGI STL容器全部使用这个接口:像是 vector,list等全部使用这个接口来处理空间的配置。

template <typename T,typename Alloc=alloc>
class vector
{
portected:
	typedef simple_alloc<T,Alloc> data_allocator	//数据空间配置器
	...
    void deallocate()
    {
        data_allocator::deallocate(xxxxx);
    }
}

deallocate函数 调用 data_allocator ,从而调用 deallocate函数,实际上就是 simple_alloc 的两个deallocate函数,然后再根据是第一级配置器还是第二级配置器选择哪个版本的 deallocate函数。

1.4.1 第一级空间配置器

我们从**__malloc_alloc_template**类 中 一层一层的看:

static void * oom_malloc(size_t);
static void *oom_realloc(void *, size_t);
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
	static void (* __malloc_alloc_oom_handler)();
#endif

oom:out of memmory 表示内存不足。

__malloc_alloc_oom_handler是一个函数指针,可以处理内存不足的情况


创建n个大小的空间,allocate会直接 malloc这个大小的空间,如果分派失败,则调用内存不足的处理方法

static void * allocate(size_t n)
{
    void *result = malloc(n);   //第一级配置器直接使用malloc分配内存
    if (0 == result) result = oom_malloc(n);//无法满足要求,使用内存不足的处理方法
    return result;
}

释放某个对象的空间,直接使用 deallocate 的free

static void deallocate(void *p, size_t /* n */)
{
	free(p);    //直接使用free释放内存
}

扩容,使用 包装的ralloc即可,同样会处理内存不足的处理情况

 static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
 {
     void * result = realloc(p, new_sz);
     if (0 == result) result = oom_realloc(p, new_sz);
     return result;
 }

发生内存不足的错误时的函数指针的处理:

可以指定 f 为 你任意的处理错误的方法,然后赋予给 函数指针。

//可以通过它指定你自己的out of memory 处理方法
//仿真 set_new_handle
static void (* set_malloc_handler(void (*f)()))()
{
    void (* old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = f;
    return(old);
}

当出现内存不足时的处理方法:

oom_malloc 和 oom_realloc 会尝试在例程中压缩出空间,如果挤出了一点内存则返回此内存,如果一点内存都挤不出来,则出发 失败的异常。

template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
    void (* my_malloc_handler)();
    void *result;

    for (;;) {//不断尝试
        my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }//失败直接抛出异常
        (*my_malloc_handler)(); //尝试调用例程,企图释放内存
        result = malloc(n);//尝试配置内存
        if (result) return(result);
    }
}
template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)
{
    void (* my_malloc_handler)();
    void *result;

    for (;;) {
        my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }//失败直接抛出异常
        (*my_malloc_handler)();
        result = realloc(p, n);
        if (result) return(result);
    }
}
#   define __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)

失败直接退出


第一级空间配置器 利用 malloc free和ralloc实现出了对内存的分配与释放。

并且也实现了C++的 new handler机制

c++ 的new_handler机制: 当无法分配足够的内存时,在丢出std::bad_alloc之前,客端会调用一些指定的处理例程,称为 new_handler,然后尽全力帮你挤出内存,如果实在挤不出来则只能抛出异常。

但是 第一级空间配置器使用的是 malloc,而不是 new,所以无法实现 C++纯正的 set_new_handler机制,因此必须手动模拟一个 set_new_handler

正如上面的 oom_malloc 和 oom_ralloc 函数的实现

第一级空间配置器使用如下的别名:malloc_alloc

//malloc_alloc 为第一级空间配置器
typedef __malloc_alloc_template<0> malloc_alloc;

1.4.2 第二级空间配置器

__default_alloc_template 作为第二级配置器用来处理避免太多小额区块造成的运行负担。

区块越小,额外负担所占用的比例就越大,就越浪费

第二级配置器的做法:

  1. 如果区块大于 128 字节,则移交第一级配置器实现

  1. 如果区块小于 128 字节,则交由内存池管理,这种方法又叫做 次级配置,每次分配一大块内存,就会产生并且维护一个自由链表

  1. 如果下次再有相同大小的区块需要分配,则直接从 free-list中取出

  1. 如果释放,则直接 回收 free-list 中的某一段

配置会自动将任何区块的内存需求量设置为 8 的倍数:30 -> 32

并且维护16块 free-list,每个块为8字节,每个块都是一个 free-list,free-list总大小即为128字节,每个free-list各自管理自己对应的 8个字节的小额区块

/*
      自由链表 : free_list
*/
union obj {
    union obj * free_list_link;
    char client_data[1];    /* The client sees this.        */
};

使用 union 来节省内存空间

free-list 指向 还没有被分配的内存空间的地址,如果已经分配,则不再指向他们


预定义 区块及free-list的个数

enum {__ALIGN = 8};//上调边界
enum {__MAX_BYTES = 128};//小型区块的上界
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};  //free_list个数

此函数表示将区块的内存需求量都上升为 8 的倍数(每块总共有8个字节,表示成能分多少块

static size_t ROUND_UP(size_t bytes) {
            return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
      }

free-list的定义 : 是一个数组,数组的每一个元素都是一个指针。

根据区块大小,决定使用第几块 free-list

static obj * __VOLATILE free_list[__NFREELISTS]; //链表:指针数组
# endif
    //决定使用第几号 free_list  从1开始
  static  size_t FREELIST_INDEX(size_t bytes) {
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
  }

数据成员:

// Chunk allocation state.
static char *start_free;  //内存池起始位置
static char *end_free;    //内存池结束位置
static size_t heap_size;

1.4.2.1 空间配置函数 allocate

allocate的执行过程:

  1. 如果需要分配的空间 n超过了128个字节的大小,则就转到第一级空间配置器的实现上。

  1. 然后再从16个 free-list中选择适当的一个。

  1. 如果free-list中有可用的区块,则直接拿来用;否则就把区块的大小上调至 8 的倍数,然后再refill重新填充 free-list。

  1. 然后调整 free-list 为选出第 n 个区块后的下一个free-list。

  1. 最后取出 result 指向的第 n 个区块的空间起始地址

 /*
    空间配置函数
 */
static void * allocate(size_t n)
{
    obj * __VOLATILE * my_free_list;
    obj * __RESTRICT result;
    //大于128 就调用第一级配置器
    if (n > (size_t) __MAX_BYTES) {
        return(malloc_alloc::allocate(n));
    }
    //从16个 free_list中选择一个使用
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    if (result == 0) {
        void *r = refill(ROUND_UP(n));//没有找到可用的free_list 准备重新填充
        return r; 
    }
    *my_free_list = result -> free_list_link;//调整free_list
    return (result);//取出空间
};

1.4.2.2 空间释放函数 deallocate

回收区块:

  1. 如果回收大于 128 个字节,则转到第一级空间配置器

  1. 寻找对应的 free-list区块

  1. 把 p 指向的某一个区块插入到free-list中

  1. 首先q的next地址为free-list对应的下一个区块

  1. free-list的当前区块转为 q区块

  1. 其实就是完成了链表的中间插入

//释放空间
static void deallocate(void *p, size_t n)
{
    obj *q = (obj *)p;
    obj * __VOLATILE * my_free_list;

    //大于128调用第一级配置器
    if (n > (size_t) __MAX_BYTES) {
        malloc_alloc::deallocate(p, n);
        return;
    }
    my_free_list = free_list + FREELIST_INDEX(n);//寻找对应的free_list
    //调整list 回收区块
    q -> free_list_link = *my_free_list;
    *my_free_list = q;
    // lock is released here
}

1.4.2.3 重新填充 refill

前面讲到从free-list中取出第 n 个区块的区块,万一free-list中已经没有了可以用的区块,则需要重新填充 free-list 自由链表

  1. 从内存池中取出nobjs个区块,作为free-list的新节点,nobjs是引用的方式传递的。

  1. 如果只获得了一个区块,则直接分配给调用者用,free-list无需新的区块,通过返回 chunk 给到allocate的返回值;否则准备调整free-list,纳入新的节点。

  1. 然后在 chunk 空间内建立 free-list

/*
返回一个大小为n的对象,并且为free_list增加节点
*/
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    int nobjs = 20;
    //nobjs通过传递引用的方式 来取得nobjs个区块作为free_list的新节点
    char * chunk = chunk_alloc(n, nobjs);

    obj * __VOLATILE * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;

    if (1 == nobjs) return(chunk);//只获得一个区块,直接返回给调用者
    //多个区块:调整free_list  纳入新节点
    my_free_list = free_list + FREELIST_INDEX(n);

      //在chunk空间里建立free_List 
      result = (obj *)chunk;
      *my_free_list = next_obj = (obj *)(chunk + n);//free_list指向新配置的空间(取自内存池)

      //将free_list各个节点串联起来
      for (i = 1; ; i++) {
        current_obj = next_obj;
        next_obj = (obj *)((char *)next_obj + n);
        if (nobjs - 1 == i) {
            current_obj -> free_list_link = 0;
            break;
        } else {
            current_obj -> free_list_link = next_obj;
        }
      }
    return(result);
}

1.4.2.4 内存池 chunk_alloc

/*
内存池中取空间给 free_list 使用
*/
template <bool threads, int inst>
char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
    char * result;
    size_t total_bytes = size * nobjs;//总的需要分配的大小
    size_t bytes_left = end_free - start_free;//内存池剩余空间

    if (bytes_left >= total_bytes) {    
        //内存池剩余容量充足 
        result = start_free;//起始
        start_free += total_bytes;//total_bytes全部分配
        return(result);
    } 
    else if (bytes_left >= size) {
        //内存池剩余容量不能容纳全部,但是可以容纳 一个及以上的块的空间

        //nobjs是引用类型,修改为实际能够供应的区块数
        nobjs = bytes_left/size;//最大能容纳的n
        total_bytes = size * nobjs;//最大能分配的空间

        result = start_free;//起始
        start_free += total_bytes;//最多能容纳的total_bytes全部分配

        return(result);
    } 
    else {
        //一个块都容纳不了! 则尝试heap中配置

        //新的空间大小为原始需要分配的空间大小的两倍
        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
        //试着让残余零头空间还有剩余价值
        if (bytes_left > 0) {
            obj * __VOLATILE * my_free_list =
                        free_list + FREELIST_INDEX(bytes_left);

            //将内存池中的剩余空间全部编入
            ((obj *)start_free) -> free_list_link = *my_free_list;
            *my_free_list = (obj *)start_free;
        }

        start_free = (char *)malloc(bytes_to_get);
        if (0 == start_free) {
            int i;
            obj * __VOLATILE * my_free_list, *p;
            // Try to make do with what we have.  That can't
            // hurt.  We do not try smaller requests, since that tends
            // to result in disaster on multi-process machines.
            for (i = size; i <= __MAX_BYTES; i += __ALIGN) {
                my_free_list = free_list + FREELIST_INDEX(i);
                p = *my_free_list;
                if (0 != p) {
                    *my_free_list = p -> free_list_link;
                    start_free = (char *)p;
                    end_free = start_free + i;

                    //递归调用自己,修复nobjs
                    return(chunk_alloc(size, nobjs));
                    // Any leftover piece will eventually make it to the
                    // right free list.
                }
            }
            /*
            一点内存都没有了
            */
	        end_free = 0;	// In case of exception.
            //最后调用第一级配置器,看看能否得到改善
            start_free = (char *)malloc_alloc::allocate(bytes_to_get);
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it
            // succeeded.
        }
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;
        return(chunk_alloc(size, nobjs));
    }
}

1.5 内存基本操作函数

STL有五个非常重要的全局函数:

  1. 用于构造的construct函数

  1. 用于析构的destroy函数

  1. uninitialized_copy :copy

  1. uninitialized_fill:fill

  1. uninitialized_fill_n:fill_n

前两个已经在对象的构造与析构中讲过了

另外的三个函数将在本节讲解。


1.5.1 uninitialized_copy

uninitialized_copy函数能够使得内存的配置对象的构造分离开。

template <class InputIterator, class ForwardIterator>
inline ForwardIterator
  uninitialized_copy(InputIterator first, InputIterator last,
                     ForwardIterator result) {
  return __uninitialized_copy(first, last, result, value_type(result));
}

将 first到 last 迭代器范围的元素拷贝到 result所指向的位置。

即 该函数将 [first,last)范围内的元素 复制一份,并把这些 复制品放入 result的空间里。

针对输入范围的每一个迭代器 i ,construct 都会执行如下操作:

  • new (ptr) T(val)

  • constuct(&*(result+(i-first)),*i),产生 *i 的复制品,放置于输出位置 result后的相对位置中

在实现任何一个容器的构造函数的时候,通常会有两个步骤:

  1. 配置内存空间的大小

  1. 使用 uninitialized_copy来在该内存区块上构造元素

uninitialized_copy 中调用的函数:

该函数 使用__type_traits 操作来推断出迭代器所指的元素类型是不是POD类型

  • 如果是 POD __true_type:则拷贝时执行最简单的内存拷贝

  • 如果是 非POD __false_type:则需要调用相应的构造函数来逐一拷贝

template <class InputIterator, class ForwardIterator, class T>
inline ForwardIterator
__uninitialized_copy(InputIterator first, InputIterator last,
                     ForwardIterator result, T*) {
  typedef typename __type_traits<T>::is_POD_type is_POD;
  return __uninitialized_copy_aux(first, last, result, is_POD());
}
template <class InputIterator, class ForwardIterator>
inline ForwardIterator 
__uninitialized_copy_aux(InputIterator first, InputIterator last,
                         ForwardIterator result,
                         __true_type) {
  return copy(first, last, result);//是POD类型
}

template <class InputIterator, class ForwardIterator>
ForwardIterator 
__uninitialized_copy_aux(InputIterator first, InputIterator last,
                         ForwardIterator result,
                         __false_type) {
  ForwardIterator cur = result;//不是POD类型
  __STL_TRY {
    for ( ; first != last; ++first, ++cur)
      construct(&*cur, *first);//逐一进行拷贝
    return cur;
  }
  __STL_UNWIND(destroy(result, cur));
}

1.5.2 uninitialized_fill

他的作用是为指定范围的元素赋予一个值:[first,last) 赋予初始值 val

与 uninitialized_copy类似,如果迭代器范围的每一个元素都指向一个未知的区域,那么会对迭代器范围的每一个元素都调用一次: construct(&*i,val),然后在这个位置上产生一个值val

同样使用value_type推断出迭代器所指元素的类型,然后传入__uninitialized_fill。

template <class ForwardIterator, class T>
inline void uninitialized_fill(ForwardIterator first, ForwardIterator last, 
                               const T& x) {
  __uninitialized_fill(first, last, x, value_type(first));
}

在__uninitialized_fill函数内部会使用 __type_traits推断类型是否为POD类型。

  • 如果是POD类型,则直接采用最简单的fill 填充的方式(STL算法)

  • 如果不是POD类型,则对每一个元素调用其构造函数

使用 first!= last ,first++ 作为for循环的参数

template <class ForwardIterator, class T>
inline void
__uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, 
                         const T& x, __true_type)
{
  fill(first, last, x);
}

template <class ForwardIterator, class T>
void
__uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, 
                         const T& x, __false_type)
{
  ForwardIterator cur = first;
  __STL_TRY {
    for ( ; cur != last; ++cur)
      construct(&*cur, x);
  }
  __STL_UNWIND(destroy(first, cur));
}

template <class ForwardIterator, class T, class T1>
inline void __uninitialized_fill(ForwardIterator first, ForwardIterator last, 
                                 const T& x, T1*) {
  typedef typename __type_traits<T1>::is_POD_type is_POD;
  __uninitialized_fill_aux(first, last, x, is_POD());               
}

另外 fill 的实现非常简单:其实就是往迭代器的范围赋值而已

template <class ForwardIterator, class T>
void fill(ForwardIterator first, ForwardIterator last, const T& value) {
  for ( ; first != last; ++first)
    *first = value;
}

1.5.3 uninitialized_fill_n

该函数接受三个参数,一个 first 表示起始位置;一个 n,表示操作的数量;一个val。

从first迭代器处开始,操作 n 个地址空间,把val赋值给这些位置。

其实与上面的uninitialized_fill是一样的,只不过上面表示范围,这个表示一个数量

template <class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n,
                                            const T& x) {
  return __uninitialized_fill_n(first, n, x, value_type(first));
}

同理使用 value_type 推断出迭代器的元素类型,然后再判断其是否为POD类型;

  • 如果是,则直接 fill_n 即可

  • 如果不是,则需要调用其 构造函数

使用 n-- ,当n=0时停止,这个for来作为循环。


注意:上面的三个函数都具有一个特性:commit or rallback

即要么对范围的操作全部成功(全部赋值成功),要么全部不成功

如果不成功,会 抛出异常,然后调用destroy,将全部的成功的元素析构掉,因为他已经失败了,不允许他一半成功,一半失败

destroy(result, cur);//uninitialized_copy
destroy(first, cur);//uninitialized_fill
destroy(first, cur);//uninitialized_fill_n

destroy 接受一个范围,然后就是我们上面所讨论的destroy的内容了


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/402860.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mac 安装python、pip、weditor

问题现象&#xff1a;执行 python3 -m weditor 报错 ➜ ~ python3 -m weditor dyld[42143]: dyld cache (null) not loaded: syscall to map cache into shared region failed dyld[42143]: Library not loaded: /System/Library/Frameworks/CoreFoundation.framework/Versio…

【前端vue2面试题】2023前端最新版vue2模块,高频24问

​ &#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;博主收集的关于vue2面试题 目录 vue2面试题 1、$route 和 $router的区别 2、一个.v…

Redis高频面试题汇总(上)

目录 1.什么是Redis? 2.为什么Redis这么快 3.分布式缓存常见的技术选型方案有哪些&#xff1f; 4.你知道 Redis 和 Memcached 的区别吗&#xff1f; 5.Redis使用场景有哪些 6.Redis 常用的数据结构有哪些&#xff1f; 7.Redis 数据类型有哪些底层数据结构&#xff1f; …

sonarqube指标详解

最近公司引入了sonar&#xff0c;作为代码质量检测工具&#xff0c;以期提高研发同学的代码质量&#xff0c;但是结果出来后&#xff0c;有些同学不清楚相应的指标内容&#xff0c;不知道应该重点关注哪些指标&#xff0c;于是查询了一下相关的资料&#xff0c;加以总结同时也分…

【数据结构】堆排序

堆是一种叫做完全二叉树的数据结构&#xff0c;可以分为大根堆&#xff0c;小根堆&#xff0c;而堆排序就是基于这种结构而产生的一种程序算法。大堆&#xff1a;每个节点的值都大于或者等于他的左右孩子节点的值小堆&#xff1a;每个结点的值都小于或等于其左孩子和右孩子结点…

扬帆优配|业务量大突破,这个行业发展明显向好

近期上市的新股&#xff0c;大都在招股阐明书里公布了本年第一季度成绩预告。 我国快递事务量本年已达200亿件 国家邮政局监测数据显现&#xff0c;到3月8日&#xff0c;本年我国快递事务量已到达200.9亿件&#xff0c;比2019年到达200亿件提前了72天&#xff0c;比2022年提前…

goland开发环境搭建及运行第一个go程序HelloWorld

1、下载和安装golang 点击进入下载页面 下载好安装包&#xff0c;点击安装。 我之前安装过低版本的安装包&#xff0c;所以这里提示要先卸载已经安装过的低版本的。 同意协议&#xff0c;继续安装。 默认安装的文件夹为C盘&#xff0c;建议更改&#xff0c;我这里更改为D盘…

YOLOv5训练大规模的遥感实例分割数据集 iSAID从切图到数据集制作及训练

最近想训练遥感实例分割&#xff0c;纵观博客发现较少相关 iSAID数据集的切分及数据集转换内容&#xff0c;思来想去应该在繁忙之中抽出时间写个详细的教程。 iSAID数据集下载 iSAID数据集链接 下载上述数据集。 百度网盘中的train和val中包含了实例和语义分割标签。 上述…

哪些职业适合创业?学习哪些技能可以自己创业?

创意行业&#xff1a;创意行业包括广告、设计、影视等领域&#xff0c;需要创新思维和创意能力&#xff0c;适合创业。学习创意思维、平面设计、影视制作等技能可以自己创业。 科技行业&#xff1a;科技行业包括互联网、人工智能、物联网等领域&#xff0c;需要技术能力和创新思…

基于JavaEE开发博客系统项目开发与设计(附源码)

文章目录1.项目介绍2.项目模块3.项目效果1.项目介绍 这是一个基于JavaEE开发的一个博客系统。实现了博客的基本功能&#xff0c;前台页面可以进行文章浏览&#xff0c;关键词搜索&#xff0c;登录注册&#xff1b;登陆后支持对文章进行感谢、评论&#xff1b;然后还可以对评论…

[网络工程师]-网络规划与设计-逻辑网络设计(二)

3、广域网技术选择 3.1广域网互连技术 3.1.1 数字数据网络 数字数据网络(Digital Data Network,DDN)是一种利用数字信道提供数据信号传输的数据传输网,是一个半永久性连接电路的公共数字数据传输网络,为用户提供了一个高质量、高带宽的数字传输通道。 利用DDN网络实现局…

【C++】7.string

1.标准库的string类 string是表示字符串的字符串类在使用string类时&#xff0c;必须包含#include头文件以及using namespace std;string类是使用char(即作为它的字符类型&#xff0c;使用它的默认char_traits和分配器类型(关于模板的更多信息&#xff0c;请参阅basic_string)…

智能网联汽车安全芯片介绍(一)

汽车的新四化(电动化、网联化、智能化、共享化)让汽车安全越来越受到重视,比如一个不太容易被破解的汽车遥控钥匙或者非接触开门等,越智能越开始需要安全。而过去的一些安全事件也凸显了安全的必要性。 黑客早已经盯上了汽车。2015年,Charlie Miller 、 Chris Valsek曾通过…

熟悉mmdetection3d数据在模型中的处理流程

目录1、搭建模型2、读取数据3、运行流程3.1 图像特征获取3.2 点云特征获取3.3 head3.4 编码bbox4、可视化5、总结本图文数据集采取KITTI数据集配置文件的介绍可以参考博主上一篇图文本图文旨在利用一条数据&#xff0c;走完整个多模态数据处理分支&#xff0c;获得bbox&#xf…

Linux内核里的传输层数据流

传输层发送数据包socket sendto系统调用应用程序通过socket调用sendto系统调用发送数据包&#xff0c;根据网络层协议调用inet_sendmsg或者inet6_sendmsg()函数&#xff0c;将数据包送入协议栈发送。SYSCALL_DEFINE6(sendto...) - net/socket.csock_sendmsg() - net/socket.cso…

compose系列教程-2. 显示图片

要在Android中使用Compose显示图片&#xff0c;需要使用Image组件。以下是一个简单的例子&#xff0c;它显示了一张图片&#xff1a; Composable fun MyApp() { val image painterResource(id R.drawable.my_image) Image(painter image, contentDescription "…

dynamics 365的增删改查

今天需要完成对dynamics 365的增删改查&#xff0c;网上一直找不到合适的教程&#xff0c;官方文档看不懂&#xff0c;实在没办法了&#xff0c;于是下载了chatgpt,对他进行了提问。 我&#xff1a;怎么用visual studio基于dynamics 365进行增删改查&#xff1f; ChatGPT 中文…

Python笔记 -- 类

文章目录1、引入2、操作属性3、继承4、将实例用作属性5、导入类1、引入 类和实例 使用类可以模拟任何东西&#xff0c;下面是一个小狗的简单类Dog&#xff0c;它表示任意小狗&#xff0c;实例my_dog表示一个具体的小狗方法 类中的函数称为方法&#xff0c;有关函数的一切均适用…

兔c同学的一篇:使用python 的 unittest模块对类和函数进行测试

文章目录1. 测试函数简单的函数测试单元测试和测试用例可通过的测试不可通过的测试测试未通过时怎么办2. 测试类各种断言方法测试一个类测试 AnonymousSurvey方法setUp()导言 在编写函数或类时&#xff0c;还可为其编写测试。通过测试&#xff0c;可以确定代码面对各种输入都能…

面试官必问--谈谈Spring Bean对象的生命周期吧

现在是时候讨论Spring Bean从产生到销毁整个过程的细节了&#xff0c;也就是Spring Bean的生命周期。在这里文哥先温馨提示&#xff1a;Spring Bean的生命周期是面试高频点之一&#xff0c;希望大家好好掌握哦~一. Spring Bean生命周期的概述如果没有Spring的环境&#xff0c;J…