new和delete
- new关键字相比于malloc()函数的优点在于,new不仅分配内存,还构造对象,即调用对象相应的构造函数。同样的,相比于free()函数,使用delete关键字时会调用对象的析构函数。
- new失败时会抛出一个异常,如果没有捕获到这个异常,程序会被终止。可以使用不抛出异常的new版本:
int* ptr { new(nothrow) int };
new失败时会返回nullptr。- 使用new关键字时,内存分配在自由存储区中:
int i { 7 };//变量i存储在栈中。 int* ptr { new int(8) };//变量ptr存储在栈中,*ptr存储在自由存储区中。 int** pp { nullptr };//二级指针pp存储在栈中。 pp = new int* ;//*pp存储在自由存储区中。 *pp = new int;//**pp存储在自由存储区中。
二维数组在栈中和自由存储区中的区别
- 内存中地址按顺序排列,并没有真正意义上的二维,所以计算机将二维数组以一维数组的方式表示。假设有一个栈上的3x3的二维数组grid:
int grid[3][3] {};
其在内存中的表示如下:
- 自由存储区上的多维数组内存分布是不连续的,假设有一个自由存储区上的3x3的二维数组grid,其在内存中的表示如下:
- 这行代码无法编译成功:
int** grid { new int[3][3] };
。
创建多维数组的正确方式为:int** grid { new int*[3] }; for(int i { 0 };i<3;++i){ grid[i] = new int[3]; }
按引用给函数传递长度以知的基于栈的数组(一个晦涩的语法)
此语法只适用于存储在栈中的数组。
void printArray(int (&array)[3]) { for (int i{ 0 }; i < 3; ++i) { std::cout << array[i] << "\n"; } } int main(){ int arr[3]{ 1,2,3 }; printArray(arr);//此处arr被编译器视为指针。 }
printArray函数只接收长度为3的数组:
可以使用函数模板让编译器自动推断基于栈的数组的大小:
template<size_t N> void printArray(int (&array)[N]) { for (int i{ 0 }; i < N; ++i) { std::cout << array[i] << "\n"; } } int main(){ int arr[4]{ 1,2,3,5 }; printArray(arr); }
智能指针
unique_ptr
- unique_ptr拥有资源的唯一所有权,因此无法复制unique_ptr。创建unique_ptr的方式为:
unique_ptr<People> people { new People() };
更好的办法是使用make_unique():auto people { make_unique<People>() };
- unique_ptr重载了解引用运算符*和箭头运算符->,因此可以像使用普通指针那样使用unique_ptr对象。
- get()方法可以直接获取底层指针。
- reset()方法可以释放底层指针并使unique_ptr指向另一个指针。
release()方法可以断开unique_ptr与底层指针的连接,返回资源的底层指针并将unique_ptr设置为nullptr。- 这行代码使用unique_ptr保存动态分配的C风格数组:
auto intArray { make_unique<int[]>(10) };
,
能使用数组下标访问元素:intArray[0]=1;
- 默认情况下,unique_ptr使用标准的new和delete分配和释放内存,可以将其改为自定义的分配和释放函数:
//自定义分配函数my_malloc int* my_malloc(int value) { std::cout << "my_malloc" << std::endl; return new int{ value }; } //自定义释放函数my_free, void my_free(int* p) { delete p; std::cout << "my_free" << std::endl; } int main(){ //模板的第二个参数为函数指针 void (*)(int*) std::unique_ptr<int, decltype(&my_free)> intPtr{ my_malloc(5),my_free }; std::cout << *intPtr << std::endl; }
shared_ptr
- shared_ptr是一个支持共享所有权的智能指针,可以被复制。当多个shared_ptr对象引用同一个资源时,它们通过引用计数来释放资源,只有当最后一个shared_ptr被销毁时,才会释放引用的资源。
- 使用自定义的释放函数时不需要将函数的类型指定为模板参数。
- 当有一个shared_ptr实例指向一个原始指针时,第二个shared_ptr只能是第一个shared_ptr的副本,而不能指向原始指针,否则当销毁这两个shared_ptr时会导致双重释放。
int* array{ new int(8) };//原始指针 std::shared_ptr<int> shared1{ array ,my_free};//使用自定义的deleter,不需要像unique_ptr那样指定模板类型参数。 auto shared2{ std::make_shared<decltype(shared1)>(shared1)};//程序正常 //加上下面这行代码会导致重复释放资源 std::shared_ptr<int> shared3{ array ,my_free };//又指向了一次原始指针
- shared_ptr支持别名,允许一个shared_ptr与另一个shared_ptr共享一个指针(拥有的指针),但指向不同的对象(存储的指针):
class Foo { public: Foo(int value) :data{ value } {}; int data{}; }; int main(){ auto foo{ std::make_shared<Foo>(42) }; //aliasing拥有的指针为foo.get(),存储的指针为&foo->data,只有当foo和aliasing都销毁时,才会释放Foo对象。 auto aliasing{ std::shared_ptr<int>{foo,& foo->data} }; std::cout << *aliasing << std::endl;//输出42 }
weak_ptr
- weak_ptr与shared_ptr有关,weak_ptr可以包含shared_ptr管理的资源的引用,但不拥有这个资源。可以使用lock()方法返回关联的shared_ptr。
- 假如要让对象返回一个指向自己的shared_ptr,以下方法是错误的:
正确的方法是使用std::enable_shared_from_this类作为父类,它提供的shared_from_this()方法可以正确地返回shared_ptr,weak_from_this()方法可以正确地返回weak_ptr。class Foo{ public: shared_ptr<Foo> getPointer(){ return shared_ptr<Foo>(this);//这行代码会导致双重释放 } };
class Foo:public enable_shared_from_this<Foo>{ public: shared_ptr<Foo> getPointer(){ return shared_from_this(); } }; int main(){ auto ptr1{ make_shared<Foo>() }; auto ptr2{ ptr1->getPointer() }; }