1. new 和 delete
在C++中,内存管理是一个核心且复杂的主题,它涉及到动态内存分配和释放。new
和delete
操作符是C++中用于管理动态内存的主要工具。
1.1 new
new
操作符用于在堆上动态分配内存,并调用对象的构造函数(如果有的话)。new
可以返回指向分配的内存的指针,该指针可以用来访问和操作对象。
例子
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int x) : value(x) {}
void display() const {
cout << "Value: " << value << endl;
}
private:
int value;
};
int main() {
// 使用new动态分配MyClass对象
MyClass* myObject = new MyClass(10);
// 使用对象
myObject->display(); // 输出: Value: 10
// ...(后续对myObject的操作)
// 不要忘记释放内存
delete myObject;
return 0;
}
在这个例子中,new
操作符被用来在堆上分配一个MyClass
类型的对象,并调用其构造函数,传递参数10。分配的内存地址被存储在指针myObject
中,然后通过该指针访问对象的display
方法。
1.2 delete
delete
操作符用于释放之前通过new
操作符分配的内存。当使用delete
时,对象的析构函数(如果有的话)会被调用,以执行必要的清理工作。
例子(继续上面的代码)
// ...(前面的代码)
// 释放内存
delete myObject;
// 注意:释放内存后,myObject成为悬垂指针(dangling pointer),
// 它不再指向有效的内存地址。为了安全起见,可以将它设置为nullptr。
myObject = nullptr;
// 尝试访问已删除的对象会导致未定义行为
// myObject->display(); // 不要这样做!
return 0;
在这个例子中,delete
操作符被用来释放myObject
指针所指向的内存。在释放内存后,将myObject
设置为nullptr
是一个好习惯,这可以防止悬垂指针的问题。
注意事项
- 匹配new和delete:对于每个
new
表达式,都应该有一个对应的delete
表达式。同样,对于new[]
(用于分配对象数组),应该使用delete[]
来释放内存。 - 避免内存泄漏:确保所有通过
new
分配的内存最终都被delete
释放。内存泄漏是程序长期运行后性能下降的常见原因之一。 - 避免重复释放:不要对同一个指针使用多次
delete
。这会导致未定义行为,通常表现为程序崩溃。 - 智能指针:C++11及更高版本引入了智能指针(如
std::unique_ptr
和std::shared_ptr
),它们可以自动管理内存,减少内存泄漏的风险。
通过合理使用new
和delete
(或智能指针),C++程序员可以精确控制程序的内存使用,编写出高效、可维护的代码。
2. 智能指针
在C++中,智能指针是管理动态内存的一种重要方式,它们能够自动释放所管理的资源,从而避免内存泄漏等问题。以下是关于智能指针的详细整理,包括通用操作、shared_ptr、unique_ptr和weak_ptr,并附有具体例子。
2.1 智能指针的通用操作
智能指针的行为类似于常规指针,但它们提供了自动资源管理的功能。智能指针一般支持以下几种通用操作:
- 解引用:使用
*
运算符可以解引用智能指针,获得其所指向的对象。 - 成员访问:使用
->
运算符可以访问智能指针所指向对象的成员。 - 条件判断:在条件判断中使用智能指针时,会检查它是否为空。
2.2 shared_ptr
shared_ptr
是一种智能指针,它允许多个shared_ptr
实例共享对同一个对象的所有权。当最后一个shared_ptr
被销毁或重置时,它所管理的对象也会被自动销毁。
2.2.1 创建与赋值
#include <memory>
#include <iostream>
int main() {
std::shared_ptr<int> p1 = std::make_shared<int>(42); // 使用make_shared创建
std::shared_ptr<int> p2 = p1; // 赋值,共享所有权
std::cout << *p1 << std::endl; // 输出: 42
std::cout << *p2 << std::endl; // 输出: 42
return 0;
}
2.2.2 自定义删除器
shared_ptr
允许你指定一个自定义的删除器,该删除器在对象被销毁时调用。
#include <memory>
#include <iostream>
void customDeleter(int* p) {
delete p;
std::cout << "Custom deleter called" << std::endl;
}
int main() {
std::shared_ptr<int> p(new int(42), customDeleter);
// ...
return 0; // 当p被销毁时,customDeleter被调用
}
2.3 unique_ptr
unique_ptr
是一种“独占”的智能指针,它在任何时候都只能有一个unique_ptr
指向一个给定的对象。一旦unique_ptr
被销毁,它所管理的对象也会被销毁。
2.3.1 初始化与赋值
#include <memory>
#include <iostream>
int main() {
std::unique_ptr<int> p1(new int(42)); // 初始化
// std::unique_ptr<int> p2 = p1; // 错误:不支持拷贝赋值
// 所有权转移
std::unique_ptr<int> p3;
p3 = std::move(p1); // 现在p3拥有对象,p1为空
std::cout << *p3 << std::endl; // 输出: 42
return 0;
}
2.3.2 自定义删除器
unique_ptr
同样支持自定义删除器。
#include <memory>
#include <iostream>
void customDeleter(int* p) {
delete p;
std::cout << "Custom deleter called" << std::endl;
}
int main() {
std::unique_ptr<int, void(*)(int*)> p(new int(42), customDeleter);
// ...
return 0; // 当p被销毁时,customDeleter被调用
}
2.4 weak_ptr 解决循环引用
weak_ptr
是一种不拥有其所指向对象所有权的智能指针,它通常与shared_ptr
一起使用来解决循环引用问题。
2.4.1 循环引用问题
当两个或多个shared_ptr
相互引用时,它们会形成循环引用,导致对象无法被正确销毁。
2.4.2 使用weak_ptr解决循环引用
#include <memory>
#include <iostream>
class A;
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
// ...
};
class B {
public:
std::weak_ptr<A> a_wptr;
// ...
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_wptr = a;
// ...
// 当a和b被销毁时,由于a_wptr不拥有b的所有权,所以b可以被正确销毁
// 然后a也可以被销毁,避免了循环引用导致的内存泄漏
return 0;
}
在上面的例子中,A
类包含一个指向B
的shared_ptr
,而B
类包含一个指向A
的weak_ptr
。这样,即使存在相互引用,也不会形成循环引用问题,因为weak_ptr
不增加shared_ptr
的引用计数。当shared_ptr
被销毁时,如果没有其他shared_ptr
指向该对象,则对象会被自动销毁,此时weak_ptr
也会变为空。