C++:allocator類(動態數組續)

1.為什么需要 allocator?

在 C++ 中,動態內存管理通常通過?new?和?delete?完成:

int* p = new int;    // 分配內存 + 構造對象
delete p;            // 析構對象 + 釋放內存

但?new?和?delete?有兩個問題:

  1. 耦合性:將內存分配和對象構造合并為一個操作。

  2. 靈活性不足:無法適配不同的內存管理策略(如內存池、共享內存等)。

allocator?類的核心目標就是解耦內存分配和對象構造,提供更靈活的內存管理。


?2.allocator 的核心作用

std::allocator?是標準庫容器(如?vector,?list,?map?等)默認使用的內存分配器。它主要有以下作用:

1. 分離內存分配和對象構造
  • 分配內存:先分配原始內存塊,但不構造對象。

  • 構造對象:在已分配的內存上手動構造對象。

  • 析構對象:手動析構對象,但不釋放內存。

  • 釋放內存:最終釋放原始內存塊。

#include <memory>std::allocator<int> alloc;// 1. 分配內存(未初始化)
int* p = alloc.allocate(5); // 分配 5 個 int 的空間// 2. 構造對象
for (int i = 0; i < 5; ++i) {alloc.construct(p + i, i); // 在 p[i] 處構造 int 對象,值為 i
}// 3. 析構對象
for (int i = 0; i < 5; ++i) {alloc.destroy(p + i);      // 析構 p[i] 處的對象
}// 4. 釋放內存
alloc.deallocate(p, 5);
2. 支持自定義內存管理策略

通過自定義?allocator,可以實現:

  • 內存池:預分配大塊內存,減少碎片。

  • 共享內存:在進程間共享內存區域。

  • 性能優化:針對特定場景優化內存分配速度。


3.allocator 的典型使用場景

場景 1:標準庫容器

所有標準庫容器(如?std::vector)默認使用?std::allocator

template <class T, class Allocator = std::allocator<T>>
class vector {
private:T* data_ = nullptr;         // 指向動態數組的指針size_t size_ = 0;          // 當前元素數量size_t capacity_ = 0;      // 當前分配的內存容量Allocator allocator_;      // 內存分配器對象public:// ... 保留已有構造函數 ...// 賦值運算符(拷貝并交換 idiom)vector& operator=(const vector& other) {if (this != &other) {vector temp(other); // 利用拷貝構造函數swap(*this, temp);  // 交換資源}return *this;}// 移動賦值運算符vector& operator=(vector&& other) noexcept {if (this != &other) {clear();allocator_.deallocate(data_, capacity_);data_ = other.data_;size_ = other.size_;capacity_ = other.capacity_;allocator_ = std::move(other.allocator_);other.data_ = nullptr;other.size_ = other.capacity_ = 0;}return *this;}// 交換兩個vector(noexcept保證)friend void swap(vector& a, vector& b) noexcept {using std::swap;swap(a.data_, b.data_);swap(a.size_, b.size_);swap(a.capacity_, b.capacity_);swap(a.allocator_, b.allocator_);}// 添加emplace_back支持template <class... Args>void emplace_back(Args&&... args) {if (size_ >= capacity_) {reserve(capacity_ ? capacity_ * 2 : 1);}allocator_.construct(data_ + size_++, std::forward<Args>(args)...);}// 完善reserve的異常安全void reserve(size_t new_cap) {if (new_cap <= capacity_) return;T* new_data = allocator_.allocate(new_cap);size_t i = 0;try {for (; i < size_; ++i) {allocator_.construct(new_data + i, std::move_if_noexcept(data_[i]));allocator_.destroy(data_ + i);}} catch (...) {// 回滾已構造元素for (size_t j = 0; j < i; ++j) {allocator_.destroy(new_data + j);}allocator_.deallocate(new_data, new_cap);throw;}allocator_.deallocate(data_, capacity_);data_ = new_data;capacity_ = new_cap;}// ... 保留其他已有方法 ...
};

要點說明:

1. 使用分配器進行內存管理(allocate/deallocate)
2. 實現RAII原則,在析構函數中釋放資源
3. 支持基礎操作:push_back/pop_back/clear
4. 包含移動語義優化性能
5. 實現迭代器訪問功能
6. 包含簡單的擴容策略(容量翻倍)
這個只是簡單模仿vector容器的核心機制,實際標準庫實現會更復雜(包含異常安全、優化策略等很多東西)

場景 2:自定義內存分配策略

例如,實現一個簡單的內存池:

template <typename T>
class MemoryPoolAllocator {
public:// 必需的類型定義(C++標準要求)using value_type = T;               // 分配的元素類型using pointer = T*;                 // 指針類型using const_pointer = const T*;     // 常指針類型using size_type = std::size_t;      // 大小類型using difference_type = std::ptrdiff_t; // 指針差異類型// 分配器傳播特性(影響容器拷貝行為)using propagate_on_container_copy_assignment = std::true_type;using propagate_on_container_move_assignment = std::true_type;using propagate_on_container_swap = std::true_type;/*** @brief 默認構造函數(必須支持)* @note 需要保證同類型的不同allocator實例可以互相釋放內存*/MemoryPoolAllocator() noexcept = default;/*** @brief 模板拷貝構造函數(必須支持)* @tparam U 模板參數類型* @note 允許從其他模板實例化的allocator進行構造*/template <typename U>MemoryPoolAllocator(const MemoryPoolAllocator<U>&) noexcept {}/*** @brief 內存分配函數(核心接口)* @param n 需要分配的元素數量* @return 指向分配內存的指針* @exception 可能拋出std::bad_alloc或派生異常*/T* allocate(size_t n) {// TODO: 實現內存池分配邏輯// 建議方案:// 1. 計算總字節數 bytes = n * sizeof(T)// 2. 從內存池獲取對齊的內存塊// 3. 返回轉換后的指針return static_cast<T*>(::operator new(n * sizeof(T)));}/*** @brief 內存釋放函數(核心接口)* @param p 需要釋放的內存指針* @param n 釋放的元素數量* @note 必須保證p是通過allocate(n)分配的指針*/void deallocate(T* p, size_t n) noexcept {// TODO: 實現內存池回收邏輯// 建議方案:// 1. 將內存塊標記為空閑// 2. 返回內存池供后續重用::operator delete(p);}/*** @brief 分配器比較函數(必須支持)* @note 不同實例是否應該被視為相等,需根據內存池實現決定*/bool operator==(const MemoryPoolAllocator&) const noexcept { return true; // 假設所有實例使用同一內存池}bool operator!=(const MemoryPoolAllocator&) const noexcept {return false;}/*** @brief 可選:對象構造函數(C++20前需要)* @tparam Args 構造參數類型*/template <typename... Args>void construct(T* p, Args&&... args) {::new(static_cast<void*>(p)) T(std::forward<Args>(args)...);}/*** @brief 可選:對象析構函數(C++20前需要)*/void destroy(T* p) {p->~T();}
};
場景 3:避免默認初始化

默認的?new?會調用構造函數,而?allocator?可以先分配內存,再按需構造對象:

std::allocator<std::string> alloc;
std::string* p = alloc.allocate(3); // 僅分配內存,不構造對象// 按需構造
alloc.construct(p, "hello");         // 構造第一個 string
alloc.construct(p + 1, "world");     // 構造第二個 string

4.allocator 的關鍵接口

以下是?std::allocator?的核心方法:

方法作用
allocate(n)分配?n?個對象的原始內存(未初始化)
deallocate(p, n)釋放內存(需先析構所有對象)
construct(p, args)在位置?p?構造對象,參數為?args
destroy(p)析構?p?處的對象

5.自定義 allocator 的要點

5.1. 必須提供的類型別名

自定義?allocator?需要定義以下類型:

template <typename T>
class CustomAllocator {
public:using value_type = T; // 必須定義// 其他必要類型...
};
5.2. 實現必要接口

至少需要實現?allocate?和?deallocate?方法:

T* allocate(size_t n) {return static_cast<T*>(::operator new(n * sizeof(T)));
}void deallocate(T* p, size_t n) {::operator delete(p);
}
5.3. 支持 rebind 機制

容器可能需要分配其他類型的對象(如鏈表節點的分配器):

template <typename U>
struct rebind {using other = CustomAllocator<U>;
};

6.C++17 后的改進

C++17 引入了?std::allocator_traits,簡化了自定義?allocator?的實現。即使自定義分配器未實現某些接口,allocator_traits?會提供默認實現:

template <typename Alloc>
using allocator_traits = std::allocator_traits<Alloc>;// 使用示例
auto p = allocator_traits<Alloc>::allocate(alloc, n);

7.總結

  • 核心作用:解耦內存分配與對象構造,提供更靈活的內存管理。

  • 默認行為:標準庫容器使用?std::allocator

  • 自定義場景:內存池、性能優化、特殊內存區域(如共享內存)。

  • 關鍵接口allocatedeallocateconstructdestroy

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/74986.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/74986.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/74986.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

北斗導航 | 中國北斗衛星導航系統的發展歷程——“三步走”戰略:背景,信號頻點,調制方式,短報文,等

中國北斗衛星導航系統的發展歷程按照“三步走”戰略逐步推進,從區域服務到全球覆蓋,形成了北斗一號、北斗二號、北斗三號三代系統的迭代升級,展現了中國航天科技的自主創新與突破。以下是各階段的核心內容與發展特點綜述:一、北斗一號:中國衛星導航的奠基(1994-2003年) …

Headless Chrome 優化:減少內存占用與提速技巧

在當今數據驅動的時代&#xff0c;爬蟲技術在各行各業扮演著重要角色。傳統的爬蟲方法往往因為界面渲染和資源消耗過高而無法滿足大規模數據采集的需求。本文將深度剖析 Headless Chrome 的優化方案&#xff0c;重點探討如何利用代理 IP、Cookie 和 User-Agent 設置實現內存占用…

英偉達GB300新寵:新型LPDDR5X SOCAMM內存

隨著人工智能&#xff08;AI&#xff09;、機器學習&#xff08;ML&#xff09;和高性能計算&#xff08;HPC&#xff09;應用的快速發展&#xff0c;對于高效能、大容量且低延遲內存的需求日益增長。NVIDIA在其GB系列GPU中引入了不同的內存模塊設計&#xff0c;以滿足這些嚴格…

靜態網頁應用開發環境搭建實戰教程

1. 前言 靜態網頁開發是前端工程師的基礎技能之一&#xff0c;無論是個人博客、企業官網還是簡單的Web應用&#xff0c;都離不開HTML、CSS和JavaScript。搭建一個高效的開發環境&#xff0c;能夠極大提升開發效率&#xff0c;減少重復工作&#xff0c;并優化調試體驗。 本教程…

Python每日一題(9)

Python每日一題 2025.3.29 一、題目二、分析三、源代碼四、deepseek答案五、源代碼與ai分析 一、題目 question["""企業發放的獎金根據利潤提成。利潤(I)低于或等于10萬元時,獎金可提10%,利潤高于10萬元,低于20萬元時,低于10萬元的部分按10%提成,高于10萬元的部…

游戲引擎學習第187天

看起來觀眾解決了上次的bug 昨天遇到了一個相對困難的bug&#xff0c;可以說它相當棘手。剛開始的時候&#xff0c;沒有立刻想到什么合適的解決辦法&#xff0c;所以今天得從頭開始&#xff0c;逐步驗證之前的假設&#xff0c;收集足夠的信息&#xff0c;逐一排查可能的原因&a…

【入門初級篇】布局類組件的使用(1)

【入門初級篇】布局類組件的使用&#xff08;1&#xff09; 視頻要點 &#xff08;1&#xff09;章節大綱介紹 &#xff08;2&#xff09;布局類組件類型介紹&#xff1a;行布局、列布局、標題 &#xff08;3&#xff09;實操演示&#xff1a;列表統計查詢布局模型 點擊訪問my…

對內核fork進程中寫時復制的理解記錄

前言 文章寫于學習Redis時對aof后臺重寫中寫時復制的疑問 一、感到不理解的歧義 在部分技術文檔中&#xff08;以小林的文章為例&#xff09;&#xff0c;對寫時復制后的內存權限存在如歧義&#xff1a; ! 二、正確技術表述 根據Linux內核實現&#xff08;5.15版本&#x…

Ditto-Talkinghead:阿里巴巴數字人技術新突破 [特殊字符]?

Ditto-Talkinghead&#xff1a;阿里巴巴數字人技術新突破 &#x1f5e3;? 阿里巴巴推出了一項新的數字人技術&#xff0c;名為 Ditto-Talkinghead。這項技術主要用于生成由音頻驅動的說話頭&#xff0c;也就是我們常說的“數字人”。不過&#xff0c;現有的基于擴散模型的同類…

.NET開發基礎知識1-10

1. 依賴注入&#xff08;Dependency Injection&#xff09; 技術知識&#xff1a;依賴注入是一種設計模式&#xff0c;它允許將對象的依賴關系從對象本身中分離出來&#xff0c;通過構造函數、屬性或方法參數等方式注入到對象中。這樣可以提高代碼的可測試性、可維護性和可擴展…

每日一題 MySQL基礎知識----(三)

數據庫常用基礎知識&#xff1a;代碼講解和實驗 1.創建數據庫student 02&#xff0c;創建一個名為student02的數據庫 CREATE DATABASE student02; 2.在student02中創建一張 students表&#xff0c;并且具有學生的編號id&#xff0c;姓名name&#xff0c;年齡age&#xff0c;生…

MySQL多表查詢實驗

1.數據準備 -- 以下語句用于創建 students 表&#xff0c;該表存儲學生的基本信息 -- 定義表名為 students CREATE TABLE students (-- 定義學生的唯一標識符&#xff0c;類型為整數&#xff0c;作為主鍵&#xff0c;且支持自動遞增student_id INT PRIMARY KEY AUTO_INCREMENT…

windows第二十章 單文檔應用程序

文章目錄 單文檔定義新建一個單文檔應用程序單文檔應用程序組成&#xff1a;APP應用程序類框架類&#xff08;窗口類&#xff09;視圖類&#xff08;窗口類&#xff0c;屬于框架的子窗口&#xff09;文檔類&#xff08;對數據進行保存讀取操作&#xff09; 直接用向導創建單文檔…

C++ 初階總復習 (16~30)

C 初階總復習 &#xff08;16~30&#xff09; 目的16. 2009. volatile關鍵字的作用17. 2010.什么是多態 簡單介紹下C的多態18. 2011. 什么是虛函數 介紹下C中虛函數的原理19. 2012 構造函數可以是虛函數嘛20. 2013.析構函數一定要是虛函數嘛&#xff1f;21. 2015. 什么是C中的虛…

第一天 Linux驅動程序簡介

目錄 一、驅動的作用 二、裸機驅動 VS linux驅動 1、裸機驅動 2、linux驅動 三、linux驅動位于哪里&#xff1f; 四、應用編程 VS 內核編程 1、共同點 2、不同點 五、linux驅動分類 1、字符設備 2、塊設備 3、網絡設備 六、Linux驅動學習難點與誤區 1、學習難點 …

PaddleX產線集成功能的使用整理

一、環境搭建 1.1 安裝paddle-gpu 需要根據安裝機器的cuda的版本&#xff0c;選擇合適的版本進行安裝 #安裝paddle-gpu 官網鏈接 https://www.paddlepaddle.org.cn/install/quick?docurl/documentation/docs/zh/install/pip/linux-pip.html python -m pip install paddle…

docker-compese 啟動mysql8.0.36與phpmyadmin,并使用web連接數據庫

1、找一個文件夾&#xff0c;比如 E:\zqy\file\mysql&#xff0c;cd到這個目錄下創建文件docker-compose.yml 2、將下面的代碼塊復制到docker-compose.yml文件中 version: 3.3 services:mysql:image: mysql:8.0.36container_name: mysqlrestart: alwaysports:- 3306:3306netw…

解決 Gradle 構建錯誤:Could not get unknown property ‘withoutJclOverSlf4J’

解決 Gradle 構建錯誤&#xff1a;Could not get unknown property ‘withoutJclOverSlf4J’ 在構建 Spring 源碼或其他基于 Gradle 的項目時&#xff0c;可能會遇到如下錯誤&#xff1a; Could not get unknown property withoutJclOverSlf4J for object of type org.gradle…

mcp 接freecad畫齒輪

from mcp.server.fastmcp import FastMCP import freecad.gears.commands import os from freecad import app from freecad import part mcp FastMCP("Demo")mcp.tool() def create_gear(num_teeth20,height10,double_helix True):"""創建一個漸開線…

【大前端系列19】JavaScript核心:Promise異步編程與async/await實踐

JavaScript核心&#xff1a;Promise異步編程與async/await實踐 系列: 「全棧進化&#xff1a;大前端開發完全指南」系列第19篇 核心: 深入理解Promise機制與async/await語法&#xff0c;掌握現代異步編程技術 &#x1f4cc; 引言 在JavaScript的世界中&#xff0c;異步編程是無…