More Effective C++ 条款08:理解各种不同意义的new和delete

More Effective C++ 条款08:理解各种不同意义的new和delete


核心思想 :C++中的"new"和"delete"有多种不同含义,包括new操作符、operator new函数、placement new以及对应的delete形式。理解这些不同形式的区别和适用场景对于正确管理内存和避免资源泄漏至关重要。

🚀 1. 问题本质分析

1.1 new的多种含义

  • new操作符(new operator):语言内置的操作符,完成内存分配和对象构造
  • operator new函数:只分配内存,不调用构造函数
  • placement new:在已分配的内存上构造对象

1.2 delete的对应形式

  • delete操作符(delete operator):调用析构函数并释放内存
  • operator delete函数:只释放内存,不调用析构函数
  • placement delete:与placement new对应,只调用析构函数不释放内存
cpp 复制代码
// 不同形式的new和delete示例
class MyClass {
public:
    MyClass() { std::cout << "Constructor\n"; }
    ~MyClass() { std::cout << "Destructor\n"; }
    
    // 自定义operator new
    static void* operator new(size_t size) {
        std::cout << "Custom operator new, size: " << size << "\n";
        return ::operator new(size);
    }
    
    // 自定义operator delete
    static void operator delete(void* ptr) {
        std::cout << "Custom operator delete\n";
        ::operator delete(ptr);
    }
};

// 使用示例
void demonstrateNews() {
    // 1. new操作符:分配内存并构造对象
    MyClass* obj1 = new MyClass;  // 调用自定义operator new,然后构造函数
    
    // 2. operator new函数:只分配内存
    void* rawMemory = MyClass::operator new(sizeof(MyClass));  // 只分配,不构造
    
    // 3. placement new:在已分配内存上构造对象
    MyClass* obj2 = new(rawMemory) MyClass;  // 在rawMemory上构造对象
    
    // 对应的删除操作
    delete obj1;                    // 调用析构函数并释放内存
    obj2->~MyClass();               // 只调用析构函数(placement delete的替代)
    MyClass::operator delete(rawMemory);  // 只释放内存
}

📦 2. 问题深度解析

2.1 各种new/delete形式的对比

形式 功能 是否调用构造函数/析构函数 是否分配/释放内存
new操作符 分配内存并构造对象
operator new 只分配内存
placement new 在指定内存位置构造对象
delete操作符 调用析构函数并释放内存
operator delete 只释放内存
placement delete 调用析构函数但不释放内存

2.2 常见误用和问题

cpp 复制代码
// ❌ 错误的使用示例
class Problematic {
public:
    Problematic() { buffer = new char[100]; }
    ~Problematic() { delete[] buffer; }
    
private:
    char* buffer;
};

void demonstrateProblems() {
    // 1. 错误:混用new操作符和operator delete
    Problematic* obj = new Problematic;
    // ::operator delete(obj);  // ❌ 错误:不会调用析构函数,导致buffer泄漏
    
    // 2. 错误:混用operator new和delete操作符
    void* memory = ::operator new(sizeof(Problematic));
    Problematic* obj2 = static_cast<Problematic*>(memory);
    // delete obj2;  // ❌ 错误:对未构造的对象调用析构函数,未定义行为
    
    // 3. 错误:错误的placement delete使用
    Problematic* obj3 = new(memory) Problematic;
    // ::operator delete(obj3);  // ❌ 错误:不会调用析构函数,导致buffer泄漏
    obj3->~Problematic();  // ✅ 正确:先调用析构函数
    ::operator delete(memory);  // ✅ 正确:再释放内存
}

⚖️ 3. 解决方案与最佳实践

3.1 正确使用模式

cpp 复制代码
// ✅ 正确的内存管理模式
class CorrectUsage {
public:
    CorrectUsage() { data = new int[100]; }
    ~CorrectUsage() { delete[] data; }
    
    // 自定义operator new(可选)
    static void* operator new(size_t size) {
        std::cout << "Allocating " << size << " bytes\n";
        return ::operator new(size);
    }
    
    // 自定义operator delete(可选)
    static void operator delete(void* ptr) {
        std::cout << "Freeing memory\n";
        ::operator delete(ptr);
    }
    
    // 自定义placement new(高级用法)
    static void* operator new(size_t size, void* location) {
        return location;  // 简单地返回指定位置
    }
    
    // 对应的placement delete(异常安全所需)
    static void operator delete(void* ptr, void* location) {
        // 通常为空实现,只在构造函数抛出异常时被调用
    }
    
private:
    int* data;
};

void demonstrateCorrectUsage() {
    // 1. 正常使用new/delete
    CorrectUsage* obj1 = new CorrectUsage;
    delete obj1;
    
    // 2. 使用operator new/delete进行低级内存管理
    void* rawMem = CorrectUsage::operator new(sizeof(CorrectUsage));
    CorrectUsage* obj2 = new(rawMem) CorrectUsage;  // placement new
    obj2->~CorrectUsage();  // 显式调用析构函数
    CorrectUsage::operator delete(rawMem);  // 释放内存
    
    // 3. 数组版本的new/delete
    CorrectUsage* array = new CorrectUsage[5];
    delete[] array;
}

3.2 高级应用:自定义内存管理

cpp 复制代码
// 内存池实现示例
class MemoryPool {
public:
    MemoryPool(size_t objectSize, size_t chunkSize = 100)
        : objectSize_(objectSize), chunkSize_(chunkSize) {
        allocateChunk();
    }
    
    ~MemoryPool() {
        for (void* chunk : chunks_) {
            ::operator delete(chunk);
        }
    }
    
    void* allocate() {
        if (freeList_ == nullptr) {
            allocateChunk();
        }
        
        void* block = freeList_;
        freeList_ = *static_cast<void**>(freeList_);
        return block;
    }
    
    void deallocate(void* block) {
        *static_cast<void**>(block) = freeList_;
        freeList_ = block;
    }
    
private:
    void allocateChunk() {
        void* chunk = ::operator new(chunkSize_ * objectSize_);
        chunks_.push_back(chunk);
        
        // 构建空闲链表
        char* p = static_cast<char*>(chunk);
        for (size_t i = 0; i < chunkSize_ - 1; ++i) {
            *reinterpret_cast<void**>(p) = p + objectSize_;
            p += objectSize_;
        }
        *reinterpret_cast<void**>(p) = freeList_;
        freeList_ = chunk;
    }
    
    size_t objectSize_;
    size_t chunkSize_;
    void* freeList_ = nullptr;
    std::vector<void*> chunks_;
};

// 使用自定义内存池的类
class PooledObject {
public:
    static void* operator new(size_t size) {
        if (size != sizeof(PooledObject)) {
            return ::operator new(size);
        }
        return pool_.allocate();
    }
    
    static void operator delete(void* ptr) {
        if (ptr == nullptr) return;
        pool_.deallocate(ptr);
    }
    
private:
    static MemoryPool pool_;
    int data[100];
};

MemoryPool PooledObject::pool_(sizeof(PooledObject));

3.3 现代C++增强

cpp 复制代码
// 使用alignas和aligned_alloc(C++17)
class AlignedObject {
public:
    AlignedObject() = default;
    
    static void* operator new(size_t size) {
        // 要求64字节对齐
        constexpr size_t alignment = 64;
        if (size != sizeof(AlignedObject)) {
            return ::operator new(size);
        }
        
        #ifdef _WIN32
            return _aligned_malloc(size, alignment);
        #else
            return aligned_alloc(alignment, size);
        #endif
    }
    
    static void operator delete(void* ptr) {
        if (ptr == nullptr) return;
        
        #ifdef _WIN32
            _aligned_free(ptr);
        #else
            free(ptr);
        #endif
    }
    
private:
    alignas(64) int data[100];  // 确保对象本身也是64字节对齐
};

// 使用std::pmr::memory_resource(C++17)
#include <memory_resource>
class PmrObject {
public:
    explicit PmrObject(std::pmr::memory_resource* mr = nullptr) {
        if (mr == nullptr) {
            mr = std::pmr::get_default_resource();
        }
        data = mr->allocate(100 * sizeof(int));
    }
    
    ~PmrObject() {
        auto mr = std::pmr::get_default_resource();
        mr->deallocate(data, 100 * sizeof(int));
    }
    
    // 使用placement new和自定义删除器
    static void* operator new(size_t size, std::pmr::memory_resource* mr) {
        return mr->allocate(size);
    }
    
    static void operator delete(void* ptr, std::pmr::memory_resource* mr) {
        mr->deallocate(ptr, sizeof(PmrObject));
    }
    
    // 常规的operator delete
    static void operator delete(void* ptr) {
        auto mr = std::pmr::get_default_resource();
        mr->deallocate(ptr, sizeof(PmrObject));
    }
    
private:
    void* data;
};

💡 关键实践原则

  1. 区分new操作符和operator new

    理解它们的不同职责:

    cpp 复制代码
    // new操作符:分配内存+构造对象
    MyClass* obj = new MyClass;
    
    // operator new:只分配内存
    void* memory = MyClass::operator new(sizeof(MyClass));
  2. 配对使用new/delete形式

    确保分配和释放方式匹配:

    cpp 复制代码
    // 匹配的new/delete对
    MyClass* obj = new MyClass;      // 使用new操作符
    delete obj;                      // 使用delete操作符
    
    void* mem = ::operator new(100); // 使用operator new
    ::operator delete(mem);          // 使用operator delete
    
    MyClass* p = new(buffer) MyClass; // 使用placement new
    p->~MyClass();                   // 手动调用析构函数(placement delete的替代)
  3. 为placement new提供对应的placement delete

    确保异常安全:

    cpp 复制代码
    class ExceptionSafe {
    public:
        // placement new
        static void* operator new(size_t size, void* location) {
            return location;
        }
        
        // 对应的placement delete(在构造函数抛出异常时调用)
        static void operator delete(void* ptr, void* location) {
            // 清理资源,但不释放内存(因为内存不是我们分配的)
        }
    };
  4. 重载operator new/delete要小心

    遵循惯例和规范:

    cpp 复制代码
    class CustomAllocator {
    public:
        // 常规operator new
        static void* operator new(size_t size) {
            if (size != sizeof(CustomAllocator)) {
                return ::operator new(size);  // 委托给全局版本
            }
            return customAllocate(size);
        }
        
        // 对应的operator delete
        static void operator delete(void* ptr) {
            if (ptr == nullptr) return;
            customDeallocate(ptr);
        }
        
        // 数组版本
        static void* operator new[](size_t size) {
            return customAllocate(size);
        }
        
        static void operator delete[](void* ptr) {
            if (ptr == nullptr) return;
            customDeallocate(ptr);
        }
    };

现代C++增强

cpp 复制代码
// 使用noexcept规范
class NoExceptAllocator {
public:
 static void* operator new(size_t size) noexcept {
     try {
         return ::operator new(size);
     } catch (...) {
         return nullptr;  // 返回nullptr而不是抛出异常
     }
 }

 static void operator delete(void* ptr) noexcept {
     ::operator delete(ptr);
 }
};

// 使用constexpr分配(C++20)
struct ConstexprAllocator {
 constexpr static void* operator new(size_t size) {
     // 在编译时分配内存(如果支持)
     // 注意:这需要编译器支持
     return nullptr; // 伪实现
 }

 constexpr static void operator delete(void* ptr) {
     // 编译时释放
 }
};

// 使用分配器感知设计
template<typename Allocator = std::allocator<MyType>>
class AllocatorAware {
public:
 using allocator_type = Allocator;

 explicit AllocatorAware(const Allocator& alloc = Allocator())
     : allocator_(alloc) {}

 // 使用分配器分配内存
 void* allocate(size_t n) {
     return allocator_.allocate(n);
 }

 void deallocate(void* p, size_t n) {
     allocator_.deallocate(static_cast<value_type*>(p), n);
 }

private:
 Allocator allocator_;
};

代码审查要点

  1. 检查new/delete是否正确配对使用
  2. 确认自定义operator new/delete是否正确处理大小和边界情况
  3. 验证placement new是否有对应的placement delete用于异常安全
  4. 检查内存分配是否考虑了对齐要求
  5. 确认资源管理类是否正确实现所有必要的new/delete形式

总结
C++提供了多种形式的new和delete,每种都有特定的用途和语义。new操作符负责分配内存和构造对象,operator new只分配内存,placement new在指定位置构造对象。对应的delete形式需要正确配对使用以确保资源正确释放。理解这些不同形式的区别对于实现高效、安全的内存管理至关重要。在现代C++中,可以利用内存池、自定义分配器、对齐分配等高级技术来优化内存使用,同时需要确保异常安全和资源管理的正确性。正确使用各种new和delete形式是编写高质量C++代码的基础。

相关推荐
汤永红8 分钟前
week4-[二维数组]平面上的点
c++·算法·平面·信睡奥赛
特立独行的猫a1 小时前
C/C++三方库移植到HarmonyOS平台详细教程
c语言·c++·harmonyos·napi·三方库·aki
谱写秋天1 小时前
VSCode+Qt+CMake详细地讲解
c++·ide·vscode·qt·编辑器
A7bert7772 小时前
【YOLOv5部署至RK3588】模型训练→转换RKNN→开发板部署
c++·人工智能·python·深度学习·yolo·目标检测·机器学习
oioihoii2 小时前
现代C++工具链实战:CMake + Conan + vcpkg依赖管理
开发语言·c++
黑客影儿3 小时前
使用UE5开发2.5D开放世界战略养成类游戏的硬件配置指南
开发语言·c++·人工智能·游戏·智能手机·ue5·游戏引擎
九离十3 小时前
STL——vector的使用(快速入门详细)
开发语言·c++·stl
十五年专注C++开发4 小时前
通信中间件 Fast DDS(二) :详细介绍
linux·c++·windows·中间件·fastdds
ajassi20004 小时前
开源 C++ QT Widget 开发(六)通讯--TCP调试
c++·qt·开源