C++中std::allocator的使用案例详解

标准库中包含一个名为allocator的类,允许我们将分配和初始化分离。使用allocator通常会提供更好的性能和更灵活的内存管理能力。

        new有一些灵活性上的局限,其中一方面表现在它将内存分配和对象构造组合在了一起。类似的,delete将对象析构和内存释放组合在了一起。我们分配单个对象时,通常希望将内存分配和对象初始化组合在一起。因为在这种情况下,我们几乎肯定知道对象应有什么值。当分配一大块内存时,我们通常计划在这块内存上按需构造对象。在此情况下,我们希望将内存分配和对象构造分离。这意味着我们可以分配大块内存,但只在真正需要时才真正执行对象的创建操作(同时付出一定开销)。一般情况下,将内存分配和对象构造组合在一起可能会导致不必要的浪费

        标准库allocator类定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。类似vector,allocator是一个模板。为了定义一个allocator对象,我们必须指明这个allocator可以分配的对象类型。当一个allocator对象分配内存时,它会根据给定的对象类型来确定恰当的内存大小和对齐位置。allocator支持的操作,如下:

        allocatro分配的内存是未构造的(unconstructed)。我们按需要在此内存中构造对象。在新标准库中,construct成员函数接受一个指针和零个或多个额外参数,在给定位置构造一个元素。额外参数用来初始化构造的对象。类似make_shared的参数,这些额外参数必须是与构造的对象的类型相匹配的合法的初始化器。

        在早期版本的标准库中,construct只接受两个参数:指向创建对象位置的指针和一个元素类型的值。因此,我们只能将一个元素拷贝到未构造空间中,而不能用元素类型的任何其它构造函数来构造一个元素。还未构造对象的情况下就使用原始内存是错误的。为了使用allocator返回的内存,我们必须用construct构造对象。使用未构造的内存,其行为是未定义的。

        当我们用完对象后,必须对每个构造的元素调用destroy来销毁它们。函数destroy接受一个指针,对执行的对象执行析构函数。我们只能对真正构造了的元素进行destroy操作。一旦元素被销毁后,就可以重新使用这部分内存来保存其它string,也可以将其归还给系统。释放内存通过调用deallocate来完成。我们传递给deallocate的指针不能为空,它必须指向由allocate分配的内存。而且,传递给deallocate的大小参数必须与调用allocate分配内存时提供的大小参数具有一样的值。

        标准库还为allocator类定义了两个伴随算法,可以在未初始化内存中创建对象。它们都定义在头文件memory中,如下:

        在C++中,内存是通过new表达式分配,通过delete表达式释放的。标准库还定义了一个allocator类来分配动态内存块。分配动态内存的程序应负责释放它所分配的内存。内存的正确释放是非常容易出错的地方:要么内存永远不会被释放,要么在仍有指针引用它时就被释放了。新的标准库定义了智能指针类型------shared_ptr、unique_ptr和weak_ptr,可令动态内存管理更为安全。对于一块内存,当没有任何用户使用它时,智能指针会自动释放它。现代C++程序应尽可能使用智能指针。

        std::allocator是标准库容器的默认内存分配器。你可以替换自己的分配器,这允许你控制标准容器分配内存的方式。

        以上内容主要摘自:《C++Primer(Fifth Edition 中文版)》第12.2.2章节

下面是从其他文章中copy的测试代码,详细内容介绍可以参考对应的reference:

#include "allocator.hpp"
#include <iostream>
#include <memory>
#include <string>
#include <vector>

namespace allocator_ {

// reference: C++ Primer(Fifth Edition) 12.2.2
int test_allocator_1()
{
	std::allocator<std::string> alloc; // 可以分配string的allocator对象
	int n{ 5 };
	auto const p = alloc.allocate(n); // 分配n个未初始化的string

	auto q = p; // q指向最后构造的元素之后的位置
	alloc.construct(q++); // *q为空字符串
	alloc.construct(q++, 10, 'c'); // *q为cccccccccc
	alloc.construct(q++, "hi"); // *q为hi

	std::cout << *p << std::endl; // 正确:使用string的输出运算符
	//std::cout << *q << std::endl; // 灾难:q指向未构造的内存
	std::cout << p[0] << std::endl;
	std::cout << p[1] << std::endl;
	std::cout << p[2] << std::endl;

	while (q != p) {
		alloc.destroy(--q); // 释放我们真正构造的string
	}

	alloc.deallocate(p, n);

	return 0;
}

int test_allocator_2()
{
	std::vector<int> vi{ 1, 2, 3, 4, 5 };

	// 分配比vi中元素所占用空间大一倍的动态内存
	std::allocator<int> alloc;
	auto p = alloc.allocate(vi.size() * 2);
	// 通过拷贝vi中的元素来构造从p开始的元素
	/* 类似拷贝算法,uninitialized_copy接受三个迭代器参数。前两个表示输入序列,第三个表示
	这些元素将要拷贝到的目的空间。传递给uninitialized_copy的目的位置迭代器必须指向未构造的
	内存。与copy不同,uninitialized_copy在给定目的位置构造元素。
	类似copy,uninitialized_copy返回(递增后的)目的位置迭代器。因此,一次uninitialized_copy调用
	会返回一个指针,指向最后一个构造的元素之后的位置。
	*/
	auto q = std::uninitialized_copy(vi.begin(), vi.end(), p);
	// 将剩余元素初始化为42
	std::uninitialized_fill_n(q, vi.size(), 42);

	return 0;
}

// reference: http://www.modernescpp.com/index.php/memory-management-with-std-allocator
int test_allocator_3()
{
	std::cout << std::endl;

	std::allocator<int> intAlloc;

	std::cout << "intAlloc.max_size(): " << intAlloc.max_size() << std::endl;
	int* intArray = intAlloc.allocate(100);

	std::cout << "intArray[4]: " << intArray[4] << std::endl;

	intArray[4] = 2011;

	std::cout << "intArray[4]: " << intArray[4] << std::endl;

	intAlloc.deallocate(intArray, 100);

	std::cout << std::endl;

	std::allocator<double> doubleAlloc;
	std::cout << "doubleAlloc.max_size(): " << doubleAlloc.max_size() << std::endl;

	std::cout << std::endl;

	std::allocator<std::string> stringAlloc;
	std::cout << "stringAlloc.max_size(): " << stringAlloc.max_size() << std::endl;

	std::string* myString = stringAlloc.allocate(3);

	stringAlloc.construct(myString, "Hello");
	stringAlloc.construct(myString + 1, "World");
	stringAlloc.construct(myString + 2, "!");

	std::cout << myString[0] << " " << myString[1] << " " << myString[2] << std::endl;

	stringAlloc.destroy(myString);
	stringAlloc.destroy(myString + 1);
	stringAlloc.destroy(myString + 2);
	stringAlloc.deallocate(myString, 3);

	std::cout << std::endl;

	return 0;
}

//
// reference: http://en.cppreference.com/w/cpp/memory/allocator
int test_allocator_4()
{
	std::allocator<int> a1;   // default allocator for ints
	int* a = a1.allocate(1);  // space for one int
	a1.construct(a, 7);       // construct the int
	std::cout << a[0] << '\n';
	a1.deallocate(a, 1);      // deallocate space for one int

	// default allocator for strings
	std::allocator<std::string> a2;

	// same, but obtained by rebinding from the type of a1
	decltype(a1)::rebind<std::string>::other a2_1;

	// same, but obtained by rebinding from the type of a1 via allocator_traits
	std::allocator_traits<decltype(a1)>::rebind_alloc<std::string> a2_2;

	std::string* s = a2.allocate(2); // space for 2 strings

	a2.construct(s, "foo");
	a2.construct(s + 1, "bar");

	std::cout << s[0] << ' ' << s[1] << '\n';

	a2.destroy(s);
	a2.destroy(s + 1);
	a2.deallocate(s, 2);

	return 0;
}

} // namespace allocator_

GitHub: https://github.com/fengbingchun/Messy_Test 

到此这篇关于C++中std::allocator的使用案例详解的文章就介绍到这了,更多相关C++中std::allocator的使用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈C++空间配置器allocator

    目录 概述 1. Allocator 的标准接口 2. SGI STL 内存分配失败的异常处理 3. SGI STL 内置轻量级内存池的实现 4. SGI STL 内存池在多线程下的互斥访问 概述 在C++中,一个对象的内存配置和释放一般都包含两个步骤,对于内存的配置,首先是调用operator new来配置内存,然后调用对象的类的构造函数进行初始化:而对于内存释放,首先是调用析构函数,然后调用 operator delete进行释放. 如以下代码: class Foo { ... }; Foo

  • C++17 使用 std::string_view避免字符串拷贝优化程序性能

    C++中std::string是日常Coding中经常使用的一个类,使用起来非常方便,但是也存在一些弊端. 如下代码,参数传递的过程发生了内存分配(Memory Allocation)和内存拷贝. void fun(const std::string& s) { std::cout << s << std::endl; } const char* ch = "hello world"; // bad way, expensive if the strin

  • C++11新特性std::make_tuple的使用

    std::tuple是C++ 11中引入的一个非常有用的结构,以前我们要返回一个包含不同数据类型的返回值,一般都需要自定义一个结构体或者通过函数的参数来返回,现在std::tuple就可以帮我们搞定. 1.引用头文件 #include <tuple> 2. Tuple初始化 std::tuple的初始化可以通过构造函数实现. // Creating and Initializing a tuple std::tuple<int, double, std::string> resul

  • C++17中的std::optional的具体使用

    直入主题 本篇之中,仅仅述及 std::optional ,其它和 variant 相关的话题以后再说吧. std::optional 也划入 variant 类别中,其实它还是谈不上可称为变体类型的,但新版本中的三大件(optional,any and variant)也可以归一类无妨. C++17 之前 在 C 时代以及早期 C++ 时代,语法层面支持的 nullable 类型可以采用指针方式: T* ,如果指针为 NULL (C++11 之后则使用 nullptr ) 就表示无值状态(em

  • c++11多线程编程之std::async的介绍与实例

    本节讨论下在C++11中怎样使用std::async来执行异步task. C++11中引入了std::async 什么是std::async std::async()是一个接受回调(函数或函数对象)作为参数的函数模板,并有可能异步执行它们. template<class Fn, class... Args> future<typename result_of<Fn(Args...)>::type> async(launch policy, Fn&& fn

  • C++中std::allocator的使用案例详解

    标准库中包含一个名为allocator的类,允许我们将分配和初始化分离.使用allocator通常会提供更好的性能和更灵活的内存管理能力.         new有一些灵活性上的局限,其中一方面表现在它将内存分配和对象构造组合在了一起.类似的,delete将对象析构和内存释放组合在了一起.我们分配单个对象时,通常希望将内存分配和对象初始化组合在一起.因为在这种情况下,我们几乎肯定知道对象应有什么值.当分配一大块内存时,我们通常计划在这块内存上按需构造对象.在此情况下,我们希望将内存分配和对象构造

  • Python 中闭包与装饰器案例详解

    项目github地址:bitcarmanlee easy-algorithm-interview-and-practice 1.Python中一切皆对象 这恐怕是学习Python最有用的一句话.想必你已经知道Python中的list, tuple, dict等内置数据结构,当你执行: alist = [1, 2, 3] 时,你就创建了一个列表对象,并且用alist这个变量引用它: 当然你也可以自己定义一个类: class House(object): def __init__(self, are

  • JVM中四种GC算法案例详解

    目录 介绍 引用计数算法(Reference counting) 算法思想: 核心思想: 优点: 缺点: 例子如图: 标记–清除算法(Mark-Sweep) 算法思想: 优点 缺点 例子如图 标记–整理算法 算法思想 优点 缺点 例子 复制算法 算法思想 优点 缺点 总结 介绍 程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行

  • Vue在echarts tooltip中添加点击事件案例详解

    目录 需求 解决方法 1.设置tooltip 2.定义hookToolTip变量 3.在methods中添加方法 4.完整代码 需求 需要在echarts tooltip点击学校的名称,跳转到详情页面:项目是从上海市---> 某个区----> 具体的学校(在最后一级的tooltip中绑定一个点击事件)  项目是用vue和echarts实现的,echarts是新版本(^5.0.2),并不能把点击事件绑定在window上 解决方法 1.设置tooltip enterable: true, //允许

  • Python中的tkinter库简单案例详解

    目录 案例一 Label & Button 标签和按钮 案例二 Entry & Text 输入和文本框 案例三 Listbox 部件 案例四 Radiobutton 选择按钮 案例五 Scale 尺度 案例六 Checkbutton 勾选项 案例七 Canvas 画布 案例八 Menubar 菜单 案例九 Frame 框架 案例十 messagebox 弹窗 案例十一 pack grid place 放置 登录窗口 TKinterPython 的 GUI 库非常多,之所以选择 Tkinte

  • Android中TelephonyManager类的用法案例详解

    本文以案例形式分析了Android中TelephonyManager类的用法.分享给大家供大家参考.具体如下: 目录结构: main.xml布局文件: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="ve

  • Hadoop中的压缩与解压缩案例详解

    目录 一:压缩的作用 1.1:压缩的原则: 1.2:MR支持的压缩编码 1.3:压缩性能的比较 1.4:压缩方式的选择 压缩可以在MapReduce作用的任意阶段启用.  二:MapReduce数据压缩 三:压缩的参数配置 3.1:设置reduce输出端的压缩格式 3.2:设置map输入的压缩方式 四:文件的压缩与解压缩案例 压缩主要关注点:压缩率,压缩速度,解压速度,是否可切片 一:压缩的作用 压缩技术能够减少底层HDFS读写字节数,减少磁盘IO,提升网络传输效率,因为磁盘IO和网络带宽是Ha

  • Java中定时器Timer致命缺点案例详解

    目录 简介 案例1:定时器打印Hello World! 线程不死问题? 案例2:单线程问题 定时器实际应用场景 学习方法心得 总结 简介 这篇文章我一直在纠结到底要不要写,不想写一来因为定时器用法比较简单,二来是面试中也不常问.后来还是决定写了主要是想把自己分析问题思路分享给大家,让大家在学习过程中能够参考,学习态度我相信大部分人没有问题,特别是正在看我博文的小伙伴那更不用说了!!给你们点个狂力赞.接下来就是学习方法了,我发现近期来咨询我问题的小伙伴学习姿势不对,所以我用Java中定时器Time

  • Python中使用Frozenset对象的案例详解

    目录 关于Frozensets 创建一个新的Frozenset对象 一旦创建了Frozenset,你就不能修改它了 与 Frozensets 一起使用的方法 Frozenset可以被转换为其他可迭代类型 Frozenset使用案例 总结 这篇文章将介绍在Python中使用 "frozenset "函数的指南,该函数返回一个新的frozenset类型的Python对象.这些对象类似于Python中的set对象,但有一些关键的区别.本文的所有代码样本都是在Ubuntu 21.04上用Pyt

  • C++中双冒号::用法案例详解

    C++中的双冒号 :: 第一种,类作用域,用来标明类的变量.函数 Human::setName(char* name); 第二种,命名空间作用域,用来注明所使用的类.函数属于哪一个命名空间的 std::cout << "Hello World" << std::endl; 第三种,全局作用域,用来区分局部.全局的.最容易被忽视的一种,很多时候写了一个全局函数或者想要调用一个全局函数,却发现IDE或者Editor找不到该函数,原因是因为局部函数与想要调用的全局函数

随机推荐