动态内存管理
在实际的软件开发过程中,应用程序的内存需求通常具有高度的动态性。开发者往往需要根据运行时的数据量动态分配内存,或在多个对象之间实现资源共享,甚至在需求不确定的情况下灵活管理内存容量。在这类场景下,传统的栈内存与静态内存的分配机制已无法满足高效与灵活并重的要求。
针对上述问题,动态内存管理应运而生,极大地提升了资源利用率和程序的可扩展性。然而,动态内存管理也带来了更高的复杂度。对于C++开发者而言,正确高效地管理动态内存是一项核心且极具挑战性的能力。
不规范的内存操作极易产生诸如内存泄漏、悬空指针(Dangling Pointer)、重复释放(Double Free)等严重的运行时错误,直接影响系统的稳定性和安全性。
理解内存的不同类型
在深入学习动态内存管理之前,我们需要了解C++程序中内存的分配方式。程序的内存主要分为三种类型:
静态内存 用于存储全局变量、类的静态数据成员以及在函数外部定义的变量。这些对象在程序启动时分配,在程序结束时销毁,生命周期贯穿整个程序运行期间。
栈内存 用于存储函数内部的非静态对象。这些对象在进入其定义的作用域时创建,在离开作用域时自动销毁。栈内存的管理完全由编译器自动处理,程序员无需关心其分配和释放。
动态内存 (也称为堆内存)则由程序在运行时显式分配和释放。动态分配的对象的生命周期完全由程序代码控制,这既提供了极大的灵活性,也带来了管理的复杂性。
假设我们要实现一个图片处理程序。用户可以上传任意数量的照片,每张照片的数量和尺寸都无法事先预测。
如果我们使用固定大小的数组,不仅可能导致内存浪费,还可能在照片数量超出预设上限时出错。
通过动态分配内存,我们可以根据上传照片的实际数量和大小,灵活地申请和释放内存,从而高效而安全地管理所有图片数据。
智能指针:现代C++的内存管理利器
智能指针的核心理念
传统的动态内存管理使用new和delete操作符,这种方式虽然灵活,但极易出错。程序员必须确保每个new都有对应的delete,必须避免使用已删除的指针,还要防止重复删除同一块内存。
智能指针的出现彻底改变了这种状况。智能指针的行为类似普通指针,但它们能够自动管理所指向的对象,在适当的时候自动释放内存。这种自动化的内存管理大大降低了内存管理错误的风险。
C++标准库提供了三种智能指针:shared_ptr允许多个指针共享同一个对象,unique_ptr独占所指向的对象,weak_ptr是一种不控制对象生命周期的弱引用。这些智能指针都定义在memory头文件中。
shared_ptr:共享所有权的智能指针
shared_ptr是最常用的智能指针,它允许多个指针安全地共享同一个动态分配的对象。当最后一个指向对象的shared_ptr被销毁时,对象会被自动删除。
让我们通过一个图书管理系统的例子来理解shared_ptr的使用:
#include <memory>
#include <string>
#include <iostream>
class Book {
public:
Book ( const string & title , const string & author )
: title_ (title), author_ (author) {
cout << "创建图书:" << title_ << endl;
}
~Book () {
cout << "销毁图书:" << title_ << endl;
}
void display () const {
cout << "《" << title_ << "》 - " << author_ << endl;
}
private:
string title_;
string author_;
};
// 创建共享的图书对象
shared_ptr < Book > createBook ( const string & title , const string & author ) {
return make_shared < Book >(title, author);
}
void demonstrateSharedPtr () {
shared_ptr < Book > book1 = createBook ( "C++程序设计" , "张教授" );
// 检查智能指针是否为空
if (book1) {
book1-> display (); // 使用箭头操作符
( * book1). display (); // 或者使用解引用操作符
}
// 创建第二个指向同一对象的shared_ptr
shared_ptr < Book > book2 = book1;
cout << "当前引用计数:" << book1. use_count () << endl; // 输出:2
book1. reset (); // 重置book1,使其不再指向任何对象
cout << "book1重置后的引用计数:" << book2. use_count () << endl; // 输出:1
// 当book2离开作用域时,Book对象会被自动销毁
}
make_shared函数的优势
创建shared_ptr最安全的方式是使用make_shared函数。这个函数在动态内存中分配一个对象,并返回指向该对象的shared_ptr:
// 推荐的方式
auto studentRecord = make_shared < Student >( "李明" , 20 , "计算机科学" );
// 等价于,但不推荐
shared_ptr < Student > studentRecord2 ( new Student ( "王华" , 21 , "数学" ));
make_shared不仅更简洁,还具有性能优势。它能够一次性分配对象和控制块的内存,而分别使用new和shared_ptr构造函数则需要两次内存分配。
引用计数机制
shared_ptr内部维护一个引用计数,记录有多少个shared_ptr指向同一个对象。每当复制一个shared_ptr时,引用计数增加;当一个shared_ptr被销毁或重置时,引用计数减少。当引用计数变为零时,对象被自动删除。
void demonstrateReferenceCounting () {
auto course = make_shared < Course >( "数据结构" );
cout << "初始引用计数:" << course. use_count () << endl; // 1
{
auto sameCourse = course; // 引用计数增加
cout << "作用域内引用计数:" << course. use_count () << endl; // 2
实际应用:资源共享的优雅解决方案
在实际项目中,shared_ptr特别适用于需要在多个对象间共享资源的场景。考虑一个多媒体播放器应用,多个播放列表可能包含相同的歌曲:
class Song {
private:
string title_;
string artist_;
size_t duration_; // 以秒为单位
public:
Song ( const string & title , const string & artist , size_t duration )
: title_ (title), artist_ (artist), duration_ (duration) {}
string getInfo () const {
在这个例子中,歌曲对象被多个播放列表共享,只有当所有播放列表都不再需要某首歌曲时,该歌曲对象才会被删除。这种设计避免了数据重复,提高了内存使用效率。
直接内存管理:理解底层机制
虽然智能指针是现代C++推荐的内存管理方式,但理解传统的new和delete操作符仍然很重要。这不仅有助于理解智能指针的工作原理,在某些特殊情况下也可能需要直接管理内存。
new操作符的工作机制
new操作符执行两个关键步骤:首先在动态内存中分配空间,然后在该空间中构造对象。让我们通过一个学生信息管理的例子来理解:
class Student {
private:
string name_;
int age_;
vector < string > courses_;
public:
Student () : name_ ( "未知" ), age_ ( 0 ) {
cout << "创建默认学生对象" << endl;
}
Student ( const string & name , int age
值初始化的重要性
对于内置类型,默认初始化和值初始化的结果可能不同:
void demonstrateInitialization () {
int* score1 = new int ; // 默认初始化,值未定义
int* score2 = new int (); // 值初始化,值为0
int* score3 = new int ( 95 ); // 直接初始化,值为95
cout << "score1: " << * score1 << endl; // 可能输出垃圾值
cout << "score2: " << *
内存管理的常见陷阱
直接使用new和delete容易产生三类严重错误:
内存泄漏 发生在忘记释放动态分配的内存时。泄漏的内存永远无法被程序重新使用,随着程序运行,可用内存逐渐减少:
void memoryLeakExample () {
for ( int i = 0 ; i < 1000 ; ++ i) {
Student * student = new Student ( "临时学生" , 18 );
// 忘记delete student; 造成内存泄漏
}
// 循环结束后,1000个Student对象的内存都无法回收
}
悬垂指针 发生在使用已经释放的内存时:
void danglingPointerExample () {
Student * student = new Student ( "王五" , 19 );
Student * anotherPtr = student; // 两个指针指向同一对象
delete student; // 释放内存
student = nullptr ; // 将指针设为null
// anotherPtr现在是悬垂指针
// anotherPtr->displayInfo(); // 未定义行为!
}
重复释放 发生在对同一块内存调用多次delete时:
void doubleDeletionExample () {
Student * student = new Student ( "赵六" , 22 );
Student * copy = student;
delete student;
// delete copy; // 错误:重复释放同一块内存
}
异常安全的内存管理
在有异常的环境中,直接内存管理变得更加困难。如果在new和delete之间抛出异常,内存可能无法正确释放:
void unsafeFunction () {
Student * student = new Student ( "异常测试" , 20 );
// 如果这里抛出异常,student永远不会被删除
riskyOperation (); // 可能抛出异常的函数
delete student; // 如果异常发生,这行代码不会执行
}
void safeFunction () {
shared_ptr < Student > student = make_shared < Student >( "安全测试" ,
这个例子清楚地展示了为什么现代C++推荐使用智能指针而不是裸指针进行内存管理。
unique_ptr:独占所有权的智能指针
unique_ptr的设计理念
unique_ptr体现了独占所有权的设计理念:在任何时刻,只能有一个unique_ptr拥有某个动态分配的对象。这种设计确保了资源的唯一所有权,避免了多个指针同时管理同一资源可能产生的问题。
让我们通过一个文件管理系统的例子来理解unique_ptr:
class FileHandler {
private:
string filename_;
bool is_open_;
public:
explicit FileHandler ( const string & filename )
: filename_ (filename), is_open_ ( true ) {
cout << "打开文件:" << filename_ << endl;
}
~FileHandler () {
if (is_open_) {
cout
所有权转移机制
unique_ptr的一个重要特性是支持所有权转移。虽然不能复制unique_ptr,但可以通过移动语义转移所有权:
class DatabaseConnection {
private:
string server_;
bool connected_;
public:
DatabaseConnection ( const string & server )
: server_ (server), connected_ ( true ) {
cout << "连接到服务器:" << server_ << endl;
}
~DatabaseConnection () {
if (connected_) {
cout <<
自定义删除器
unique_ptr允许指定自定义的删除器,这在管理需要特殊清理逻辑的资源时非常有用:
// 模拟网络连接资源
struct NetworkConnection {
int socket_fd;
string remote_address;
NetworkConnection ( const string & addr ) : remote_address (addr) {
socket_fd = rand () % 1000 ; // 模拟socket描述符
cout << "建立网络连接到 " << remote_address
<< ",socket fd: " << socket_fd << endl;
}
};
weak_ptr:打破循环引用的利器
循环引用问题
在使用shared_ptr时,可能会遇到循环引用的问题。当两个或多个对象相互持有shared_ptr时,它们的引用计数永远不会变为零,导致内存泄漏。
考虑一个课程和学生的双向关系:
// 问题版本:会产生循环引用
class Student ;
class Course ;
class Course {
private:
string name_;
vector < shared_ptr < Student >> enrolled_students_;
public:
Course ( const string & name ) : name_ (name) {
cout << "创建课程:" << name_ << endl;
}
这种设计会导致课程对象持有学生对象的shared_ptr,而学生对象也持有课程对象的shared_ptr,形成循环引用,两个对象都无法被正确销毁。
weak_ptr的解决方案
weak_ptr是一种不控制对象生命周期的智能指针。它可以观察由shared_ptr管理的对象,但不会影响对象的引用计数。使用weak_ptr可以有效打破循环引用:
// 解决方案:使用weak_ptr打破循环引用
class StudentFixed ;
class CourseFixed ;
class CourseFixed {
private:
string name_;
vector < weak_ptr < StudentFixed >> enrolled_students_; // 使用weak_ptr
public:
CourseFixed ( const string & name ) : name_ (name) {
cout << "创建课程:" << name_ << endl;
}
weak_ptr的典型应用场景
weak_ptr在实现观察者模式时特别有用:
class EventPublisher ;
class EventSubscriber {
private:
string name_;
weak_ptr < EventPublisher > publisher_;
public:
EventSubscriber ( const string & name ) : name_ (name) {}
void subscribe ( shared_ptr < EventPublisher > pub ) {
publisher_ = pub; // 不增加引用计数
}
动态数组管理
动态数组的分配与初始化
当需要在运行时确定数组大小时,动态数组提供了灵活的解决方案。C++使用new[]操作符分配动态数组:
void demonstrateDynamicArrays () {
// 获取数组大小
cout << "请输入学生人数:" ;
size_t studentCount;
cin >> studentCount;
// 分配动态数组
int* scores = new int [studentCount]; // 默认初始化,值未定义
int* grades = new int [studentCount](); // 值初始化,所有元素为0
string * names = new string[studentCount]; // 调用默认构造函数
重要提醒 :动态数组必须使用delete[]而不是delete来释放。忘记使用方括号是常见的错误,可能导致未定义行为。
零长度数组的特殊情况
C++允许分配零长度的动态数组,这在某些算法中很有用:
void demonstrateZeroLengthArray () {
size_t n = 0 ; // 假设某种情况下需要分配0个元素
int* arr = new int [n]; // 合法,返回有效的非空指针
// 这个指针可以用于指针运算,但不能解引用
int* end = arr + n; // 指向"末尾后一位"
// 可以进行比较
if (arr == end) {
cout << "空数组的开始和结束指针相等" << endl;
}
智能指针与动态数组
unique_ptr对数组的支持
unique_ptr提供了专门用于管理动态数组的版本:
void uniquePtrWithArrays () {
size_t arraySize = 10 ;
// 管理动态数组的unique_ptr
unique_ptr <int [] > scores ( new int [arraySize]);
// 使用下标操作符访问元素
for ( size_t i = 0 ; i < arraySize; ++ i) {
scores[i] = rand () % 100 ;
}
shared_ptr与数组
shared_ptr默认不支持数组,但可以通过自定义删除器来管理数组:
void sharedPtrWithArrays () {
size_t arraySize = 15 ;
// 需要提供自定义删除器
shared_ptr <int> scores ( new int [arraySize], []( int* p ) { delete[] p; });
// 或者使用default_delete
shared_ptr <int> grades ( new int [arraySize], default_delete < int []>());
// 注意:shared_ptr没有下标操作符,需要使用get()
int*
allocator类:精细化内存管理
分离分配与构造
标准的new操作符将内存分配和对象构造绑定在一起,但有时我们希望将这两个步骤分开进行。allocator类提供了这种能力,允许我们先分配原始内存,然后根据需要在其中构造对象。
#include <memory>
class StudentRecord {
private:
string name_;
int id_;
vector <double> grades_;
public:
StudentRecord () = default ;
StudentRecord ( const string & name , int id )
: name_ (name), id_ (id) {
cout << "构造学生记录:"
allocator算法的实际应用
标准库还提供了一些专门用于未初始化内存的算法:
void demonstrateAllocatorAlgorithms () {
allocator < string > alloc;
size_t size = 10 ;
string * memory = alloc. allocate (size);
// 准备源数据
vector < string > subjects = { "数学" , "物理" , "化学" , "生物" , "历史" };
这种精细化的内存管理在实现自定义容器类时特别有用,可以避免不必要的构造和析构开销。
最佳实践
优先使用智能指针 :在现代C++中,应该优先使用智能指针而不是裸指针进行动态内存管理。shared_ptr适用于需要共享所有权的场景,unique_ptr适用于独占所有权的情况。
避免混合使用 :不要将智能指针和裸指针混合使用。一旦将内存的所有权交给智能指针,就不应该再使用裸指针访问该内存。
谨慎使用get() :智能指针的get()方法返回底层的裸指针,但这个指针不应该用于初始化其他智能指针或手动删除内存。
及时打破循环引用 :在可能产生循环引用的场景中,合理使用weak_ptr来打破循环。
性能考虑
虽然智能指针提供了安全性,但也会带来一定的开销。在性能敏感的场景中,需要权衡安全性和性能:
// 性能测试示例
void performanceComparison () {
const size_t iterations = 1000000 ;
// 测试裸指针的性能
auto start = chrono :: high_resolution_clock :: now ();
for ( size_t i = 0 ; i < iterations; ++ i) {
int* p = new int ( 42 );
调试技巧
在开发过程中,智能指针的一些特性可以帮助调试内存相关的问题:
void debuggingTips () {
// 检查引用计数
auto resource = make_shared < ExpensiveResource >();
cout << "初始引用计数:" << resource. use_count () << endl;
{
auto copy = resource;
cout << "复制后引用计数:" << resource. use_count () << endl;
vector < shared_ptr
通过掌握这些动态内存管理的概念和技术,我们能够编写更加安全、高效和可维护的C++程序。智能指针不仅简化了内存管理,还帮助我们避免了许多常见的内存相关错误,使得程序更加健壮可靠。
练习
现在让我们通过一些简单的练习题来巩固本章学到的知识。每道题都涵盖了智能指针的核心概念,请先尝试独立完成,然后再查看答案。
练习 1:unique_ptr的基本使用
编写一个程序,使用unique_ptr管理一个动态分配的整数,并输出它的值。
#include <iostream>
#include <memory>
using namespace std ;
int main () {
// 使用unique_ptr管理动态分配的内存
unique_ptr <int> ptr ( new int ( 42 ));
cout << "指针指向的值:" << * ptr << endl;
// unique_ptr会在作用域结束时自动释放内存
// 不需要手动delete
return 0 ;
练习 2:shared_ptr的基本使用
编写一个程序,使用shared_ptr让多个指针共享同一个对象,观察引用计数的变化。
#include <iostream>
#include <memory>
using namespace std ;
int main () {
// 创建shared_ptr
shared_ptr <int> ptr1 = make_shared < int >( 100 );
cout << "ptr1的引用计数:" << ptr1. use_count () << endl;
{
// 创建另一个shared_ptr指向同一个对象
shared_ptr <int> ptr2 =
练习 3:shared_ptr和unique_ptr的区别
解释为什么下面的代码中,unique_ptr不能复制,而shared_ptr可以?
#include <iostream>
#include <memory>
using namespace std ;
int main () {
// unique_ptr不能复制
unique_ptr <int> ptr1 ( new int ( 10 ));
// unique_ptr<int> ptr2 = ptr1; // 错误!不能复制
// 但可以移动
unique_ptr <int> ptr2 = move (ptr1); // 正确,ptr1变为空
cout << "ptr2的值:" << * ptr2 <<
练习 4:weak_ptr的使用
编写一个程序,使用shared_ptr和weak_ptr,演示weak_ptr如何避免循环引用。
#include <iostream>
#include <memory>
using namespace std ;
int main () {
shared_ptr <int> shared = make_shared < int >( 100 );
// 创建weak_ptr,不会增加引用计数
weak_ptr <int> weak = shared;
cout << "shared的引用计数:" << shared. use_count () << endl; // 输出:1
练习 5:综合应用——简单的资源管理
编写一个程序,使用unique_ptr管理一个动态分配的数组,并访问数组元素。
#include <iostream>
#include <memory>
using namespace std ;
int main () {
// 使用unique_ptr管理动态分配的数组
unique_ptr <int [] > arr ( new int [ 5 ]{ 1 , 2 , 3 , 4 , 5 });
cout << "数组元素:" << endl;
for
vector < shared_ptr < Course >> courseList;
courseList. push_back (course); // 引用计数再次增加
cout << "添加到容器后:" << course. use_count () << endl; // 3
} // sameCourse被销毁,引用计数减少
cout << "作用域外引用计数:" << course. use_count () << endl; // 1
}
return
title_
+
" - "
+
artist_;
}
};
class Playlist {
private:
string name_;
vector < shared_ptr < Song >> songs_;
public:
Playlist ( const string & name ) : name_ (name) {}
void addSong ( shared_ptr < Song > song ) {
songs_. push_back (song);
}
void listSongs () const {
cout << "播放列表:" << name_ << endl;
for ( const auto& song : songs_) {
cout << "- " << song-> getInfo () << endl;
}
}
};
void musicPlayerExample () {
// 创建一些歌曲
auto song1 = make_shared < Song >( "青花瓷" , "周杰伦" , 234 );
auto song2 = make_shared < Song >( "东风破" , "周杰伦" , 267 );
auto song3 = make_shared < Song >( "稻香" , "周杰伦" , 223 );
// 创建不同的播放列表,可以包含相同的歌曲
Playlist favorites ( "我的最爱" );
favorites. addSong (song1);
favorites. addSong (song2);
Playlist jchou ( "周杰伦精选" );
jchou. addSong (song1); // 同一首歌在不同播放列表中
jchou. addSong (song2);
jchou. addSong (song3);
favorites. listSongs ();
jchou. listSongs ();
// 歌曲对象在所有播放列表都不再引用时才会被销毁
}
) :
name_
(name),
age_
(age) {
cout << "创建学生:" << name_ << ",年龄:" << age_ << endl;
}
~Student () {
cout << "销毁学生:" << name_ << endl;
}
void addCourse ( const string & course ) {
courses_. push_back (course);
}
void displayInfo () const {
cout << "姓名:" << name_ << ",年龄:" << age_ << endl;
cout << "选课:" ;
for ( const auto& course : courses_) {
cout << course << " " ;
}
cout << endl;
}
};
void demonstrateNewOperator () {
// 动态分配单个对象
Student * student1 = new Student; // 默认初始化
Student * student2 = new Student ( "张三" , 20 ); // 带参数的初始化
Student * student3 = new Student{ "李四" , 21 }; // 列表初始化(C++11)
// 使用动态分配的对象
student2-> addCourse ( "数据结构" );
student2-> addCourse ( "算法分析" );
student2-> displayInfo ();
// 必须手动释放内存
delete student1;
delete student2;
delete student3;
}
score2
<<
endl;
// 输出:0
cout << "score3: " << * score3 << endl; // 输出:95
delete score1;
delete score2;
delete score3;
}
20
);
// 即使这里抛出异常,student也会被自动销毁
riskyOperation ();
// 无需手动删除
}
<<
"关闭文件:"
<<
filename_
<<
endl;
}
}
void write ( const string & content ) {
if (is_open_) {
cout << "写入文件 " << filename_ << ":" << content << endl;
}
}
void close () {
if (is_open_) {
cout << "手动关闭文件:" << filename_ << endl;
is_open_ = false ;
}
}
};
void demonstrateUniquePtr () {
// 创建unique_ptr
unique_ptr < FileHandler > file1 ( new FileHandler ( "config.txt" ));
// 更好的方式是使用make_unique(C++14)
auto file2 = make_unique < FileHandler >( "data.txt" );
// 使用unique_ptr
file1-> write ( "配置信息" );
file2-> write ( "数据内容" );
// unique_ptr不能复制
// unique_ptr<FileHandler> file3 = file1; // 编译错误
// 但可以移动所有权
unique_ptr < FileHandler > file3 = std :: move (file1);
// 现在file1为空,file3拥有原来file1的对象
if ( ! file1) {
cout << "file1现在为空" << endl;
}
if (file3) {
file3-> write ( "通过file3写入" );
}
// 文件会在unique_ptr销毁时自动关闭
}
"断开与服务器的连接:"
<<
server_
<<
endl;
}
}
void query ( const string & sql ) {
if (connected_) {
cout << "执行查询:" << sql << endl;
}
}
void disconnect () {
if (connected_) {
cout << "手动断开连接:" << server_ << endl;
connected_ = false ;
}
}
};
unique_ptr < DatabaseConnection > createConnection ( const string & server ) {
return make_unique < DatabaseConnection >(server);
}
unique_ptr < DatabaseConnection > migrateConnection ( unique_ptr < DatabaseConnection > conn ) {
if (conn) {
conn-> query ( "SELECT * FROM users" );
}
return conn; // 所有权转移给调用者
}
void ownershipTransferExample () {
auto db1 = createConnection ( "192.168.1.100" );
auto db2 = migrateConnection ( std :: move (db1));
// db1现在为空,db2拥有数据库连接
if ( ! db1) {
cout << "db1已转移所有权" << endl;
}
if (db2) {
db2-> query ( "SELECT * FROM products" );
}
}
// 自定义删除器
void closeNetworkConnection ( NetworkConnection * conn ) {
if (conn) {
cout << "关闭网络连接到 " << conn->remote_address
<< ",socket fd: " << conn->socket_fd << endl;
delete conn;
}
}
void customDeleterExample () {
// 使用自定义删除器
unique_ptr < NetworkConnection, decltype (closeNetworkConnection) *>
connection ( new NetworkConnection ( "api.example.com" ), closeNetworkConnection);
// 或者使用lambda表达式
auto connection2 = unique_ptr < NetworkConnection , function < void ( NetworkConnection * )>>(
new NetworkConnection ( "database.example.com" ),
[]( NetworkConnection * conn ) {
if (conn) {
cout << "Lambda删除器:关闭连接到 " << conn->remote_address << endl;
delete conn;
}
}
);
// 连接会在unique_ptr销毁时通过自定义删除器正确关闭
}
~Course () {
cout << "销毁课程:" << name_ << endl;
}
void enrollStudent ( shared_ptr < Student > student );
string getName () const { return name_; }
void listStudents () const ;
};
class Student {
private:
string name_;
vector < shared_ptr < Course >> enrolled_courses_; // 这里会造成循环引用
public:
Student ( const string & name ) : name_ (name) {
cout << "创建学生:" << name_ << endl;
}
~Student () {
cout << "销毁学生:" << name_ << endl;
}
void enrollInCourse ( shared_ptr < Course > course ) {
enrolled_courses_. push_back (course);
course-> enrollStudent ( shared_from_this ()); // 需要继承enable_shared_from_this
}
string getName () const { return name_; }
void listCourses () const {
cout << name_ << "选修的课程:" ;
for ( const auto& course : enrolled_courses_) {
cout << course-> getName () << " " ;
}
cout << endl;
}
};
~CourseFixed () {
cout << "销毁课程:" << name_ << endl;
}
void enrollStudent ( shared_ptr < StudentFixed > student ) {
enrolled_students_. push_back (student); // weak_ptr可以从shared_ptr构造
}
string getName () const { return name_; }
void listStudents () const {
cout << name_ << "的学生:" ;
for ( auto& weak_student : enrolled_students_) {
if ( auto student = weak_student. lock ()) { // 尝试获取shared_ptr
cout << student-> getName () << " " ;
} else {
cout << "[已删除的学生] " ;
}
}
cout << endl;
}
void removeExpiredStudents () {
enrolled_students_. erase (
remove_if (enrolled_students_. begin (), enrolled_students_. end (),
[]( const weak_ptr < StudentFixed > & wp ) { return wp. expired (); }),
enrolled_students_. end ()
);
}
};
class StudentFixed : public enable_shared_from_this < StudentFixed > {
private:
string name_;
vector < shared_ptr < CourseFixed >> enrolled_courses_;
public:
StudentFixed ( const string & name ) : name_ (name) {
cout << "创建学生:" << name_ << endl;
}
~StudentFixed () {
cout << "销毁学生:" << name_ << endl;
}
void enrollInCourse ( shared_ptr < CourseFixed > course ) {
enrolled_courses_. push_back (course);
course-> enrollStudent ( shared_from_this ());
}
string getName () const { return name_; }
void listCourses () const {
cout << name_ << "选修的课程:" ;
for ( const auto& course : enrolled_courses_) {
cout << course-> getName () << " " ;
}
cout << endl;
}
};
void demonstrateWeakPtr () {
auto course = make_shared < CourseFixed >( "数据结构" );
auto student1 = make_shared < StudentFixed >( "张三" );
auto student2 = make_shared < StudentFixed >( "李四" );
// 建立关系
student1-> enrollInCourse (course);
student2-> enrollInCourse (course);
course-> listStudents ();
student1-> listCourses ();
// 删除一个学生
student1. reset ();
cout << "删除学生后:" << endl;
course-> listStudents (); // 会显示已删除的学生
course-> removeExpiredStudents ();
cout << "清理过期引用后:" << endl;
course-> listStudents ();
// 课程和剩余学生会被正确销毁
}
void handleEvent ( const string & event ) {
cout << name_ << " 收到事件:" << event << endl;
}
void checkPublisher () {
if ( auto pub = publisher_. lock ()) {
cout << name_ << " 的发布者仍然存在" << endl;
} else {
cout << name_ << " 的发布者已不存在" << endl;
}
}
string getName () const { return name_; }
};
class EventPublisher {
private:
string name_;
vector < weak_ptr < EventSubscriber >> subscribers_;
public:
EventPublisher ( const string & name ) : name_ (name) {}
void addSubscriber ( shared_ptr < EventSubscriber > subscriber ) {
subscribers_. push_back (subscriber);
subscriber-> subscribe ( shared_from_this ());
}
void publishEvent ( const string & event ) {
cout << name_ << " 发布事件:" << event << endl;
for ( auto& weak_sub : subscribers_) {
if ( auto subscriber = weak_sub. lock ()) {
subscriber-> handleEvent (event);
}
}
}
void cleanupSubscribers () {
subscribers_. erase (
remove_if (subscribers_. begin (), subscribers_. end (),
[]( const weak_ptr < EventSubscriber > & wp ) { return wp. expired (); }),
subscribers_. end ()
);
}
};
// C++11支持的初始化方式
int* fixedScores = new int [ 5 ]{ 90 , 85 , 92 , 78 , 88 };
// 使用数组
for ( size_t i = 0 ; i < studentCount; ++ i) {
cout << "请输入第" << (i + 1 ) << "个学生的姓名:" ;
cin >> names[i];
cout << "请输入成绩:" ;
cin >> scores[i];
}
// 计算平均分
double total = 0 ;
for ( size_t i = 0 ; i < studentCount; ++ i) {
total += scores[i];
}
cout << "平均分:" << total / studentCount << endl;
// 释放动态数组 - 注意使用delete[]
delete[] scores;
delete[] grades;
delete[] names;
delete[] fixedScores;
}
// 必须释放,即使是零长度数组
delete[] arr;
}
// 显示数组内容
for ( size_t i = 0 ; i < arraySize; ++ i) {
cout << "scores[" << i << "] = " << scores[i] << endl;
}
// 自动使用delete[]释放内存
}
raw_ptr
=
scores.
get
();
for ( size_t i = 0 ; i < arraySize; ++ i) {
raw_ptr[i] = 80 + i;
}
// 显示内容
for ( size_t i = 0 ; i < arraySize; ++ i) {
cout << "scores[" << i << "] = " << * (raw_ptr + i) << endl;
}
// 数组会通过自定义删除器正确释放
}
<<
name_
<<
"(ID:"
<<
id_
<<
")"
<<
endl;
}
~StudentRecord () {
if ( ! name_. empty ()) {
cout << "销毁学生记录:" << name_ << endl;
}
}
void addGrade ( double grade ) {
grades_. push_back (grade);
}
void display () const {
cout << "学生:" << name_ << ",ID:" << id_
<< ",成绩数:" << grades_. size () << endl;
}
};
void demonstrateAllocator () {
allocator < StudentRecord > alloc;
size_t capacity = 5 ;
// 分配原始内存,足够容纳5个StudentRecord对象
StudentRecord * records = alloc. allocate (capacity);
// 在原始内存中构造对象
StudentRecord * current = records;
alloc. construct (current ++ , "张三" , 1001 );
alloc. construct (current ++ , "李四" , 1002 );
alloc. construct (current ++ , "王五" , 1003 );
// 注意:只构造了3个对象,还有2个位置的内存未使用
size_t constructed_count = 3 ;
// 使用已构造的对象
for ( size_t i = 0 ; i < constructed_count; ++ i) {
records[i]. addGrade ( 85.5 + i * 2 );
records[i]. display ();
}
// 销毁已构造的对象
for ( size_t i = 0 ; i < constructed_count; ++ i) {
alloc. destroy (records + constructed_count - 1 - i); // 逆序销毁
}
// 释放原始内存
alloc. deallocate (records, capacity);
}
// 使用uninitialized_copy复制数据到未初始化的内存
string * end_copy = uninitialized_copy (subjects. begin (), subjects. end (), memory);
// 使用uninitialized_fill_n填充剩余空间
uninitialized_fill_n (end_copy, size - subjects. size (), "待定课程" );
// 显示结果
cout << "构造的课程列表:" << endl;
for ( size_t i = 0 ; i < size; ++ i) {
cout << i + 1 << ". " << memory[i] << endl;
}
// 销毁所有对象
for ( size_t i = 0 ; i < size; ++ i) {
alloc. destroy (memory + size - 1 - i);
}
// 释放内存
alloc. deallocate (memory, size);
}
delete
p;
}
auto end = chrono :: high_resolution_clock :: now ();
auto raw_time = chrono :: duration_cast < chrono :: microseconds >(end - start);
// 测试unique_ptr的性能
start = chrono :: high_resolution_clock :: now ();
for ( size_t i = 0 ; i < iterations; ++ i) {
auto p = make_unique < int >( 42 );
// 自动销毁
}
end = chrono :: high_resolution_clock :: now ();
auto unique_time = chrono :: duration_cast < chrono :: microseconds >(end - start);
// 测试shared_ptr的性能
start = chrono :: high_resolution_clock :: now ();
for ( size_t i = 0 ; i < iterations; ++ i) {
auto p = make_shared < int >( 42 );
// 自动销毁
}
end = chrono :: high_resolution_clock :: now ();
auto shared_time = chrono :: duration_cast < chrono :: microseconds >(end - start);
cout << "性能比较(" << iterations << " 次操作):" << endl;
cout << "裸指针:" << raw_time. count () << " 微秒" << endl;
cout << "unique_ptr:" << unique_time. count () << " 微秒" << endl;
cout << "shared_ptr:" << shared_time. count () << " 微秒" << endl;
}
<
ExpensiveResource
>>
container;
container. push_back (resource);
cout << "添加到容器后:" << resource. use_count () << endl;
}
cout << "作用域结束后:" << resource. use_count () << endl;
// 检查weak_ptr状态
weak_ptr < ExpensiveResource > weak_ref = resource;
cout << "weak_ptr是否过期:" << weak_ref. expired () << endl;
resource. reset ();
cout << "重置shared_ptr后,weak_ptr是否过期:" << weak_ref. expired () << endl;
}
}
unique_ptr是独占所有权的智能指针,当它离开作用域时会自动释放所管理的对象。它不能被复制,只能移动,这确保了同一时刻只有一个unique_ptr拥有对象。
ptr1;
cout << "ptr2创建后,引用计数:" << ptr1. use_count () << endl;
shared_ptr <int> ptr3 = ptr1;
cout << "ptr3创建后,引用计数:" << ptr1. use_count () << endl;
} // ptr2和ptr3离开作用域,引用计数减少
cout << "ptr2和ptr3销毁后,引用计数:" << ptr1. use_count () << endl;
return 0 ;
}
shared_ptr允许多个指针共享同一个对象,使用引用计数来管理对象的生命周期。当引用计数变为0时,对象会被自动释放。use_count()方法可以查看当前的引用计数。make_shared是创建shared_ptr的推荐方式。
endl;
// shared_ptr可以复制
shared_ptr <int> sptr1 = make_shared < int >( 20 );
shared_ptr <int> sptr2 = sptr1; // 正确!可以复制
cout << "sptr1和sptr2都指向:" << * sptr1 << endl;
return 0 ;
}
unique_ptr采用独占所有权模式,同一时刻只能有一个unique_ptr拥有对象,所以不能复制,只能移动。这样可以避免多个指针同时管理同一个对象,减少复杂性。shared_ptr采用共享所有权模式,允许多个指针共享同一个对象,所以可以复制,通过引用计数管理生命周期。
// 使用weak_ptr访问对象
if ( auto locked = weak. lock ()) { // lock()返回shared_ptr
cout << "通过weak_ptr访问:" << * locked << endl;
cout << "lock后,引用计数:" << shared. use_count () << endl; // 输出:2
} // locked离开作用域,引用计数恢复为1
// 释放shared_ptr
shared. reset ();
cout << "shared释放后,weak是否有效:" << (weak. expired () ? "否" : "是" ) << endl;
return 0 ;
}
weak_ptr是对shared_ptr的弱引用,不会增加引用计数。它主要用于打破循环引用。使用lock()方法可以获取一个shared_ptr来访问对象,如果对象已被释放,lock()返回空的shared_ptr。expired()方法可以检查对象是否已被释放。
(
int
i
=
0
; i
<
5
; i
++
) {
cout << arr[i] << " " ; // 可以直接使用下标访问
}
cout << endl;
// unique_ptr会在作用域结束时自动释放数组
// 不需要手动delete[]
return 0 ;
}
unique_ptr可以管理动态分配的数组,使用unique_ptr<T[]>的形式。当它离开作用域时,会自动调用delete[]释放数组内存。这比手动管理内存更安全,避免了内存泄漏的风险。智能指针是现代C++推荐的内存管理方式。
动态内存管理 | 自在学