内存管理
理解内存
下面的代码在栈上面创建了初始化为nullptr的变量ptr,然后为ptr指向堆上的内存。
int* ptr { nullptr };
ptr = new int;或者int* ptr { new int };
虽然ptr指向堆上的内存,但是ptr仍然位于栈上。
注意:指针只是变量,可以存在于堆上或者栈上。而动态内存总是分配在堆上
下面的代码段代码首先声明了一个指向整型指针的指针作为变量 handle,动态地分配足够的内存来存储一个指向整型的指针,并将指向新内存的指针存储在 handle 中。接下来,内存 (*handle
)
分配了一个指向另一个足够大的动态内存区域的指针,该区域可以存储整数。
int** ptr {nullptr};
ptr = new int*;
*ptr = new int;
- ptr 接受的是 new int* 返回的 一个
int**
类型的指针 *ptr
接受 new int 的返回值int*
(指向整数的指针)- ptr 是指向int* 型变量 的指针
*ptr
是指向整型变量的指针**ptr
是整形变量的值
delete *handle; // 释放 0x3000 的整型数据
delete handle; // 释放 0x2000 的指针内存
handle = nullptr; // 安全重置
分配和释放
malloc&free和new&delete的主要区别
new不仅分配内存,还会构造对象
malloc只是为指定的大小分配了一块内存,不知道也不关心对象
delete会调用对象的析构函数,free不会调用
malloc&free是函数 new&delete是操作符
数组内存的分配与释放
简单的一个Simple类
class Simple
{
public:Simple() {std::cout << " Simple construct function..."<< std::endl;}~Simple() {std::cout << " Simple destory construct function..."<< std::endl;}
};
下面这种创建方式相当于在栈上创建了一个Simple* 类型的变量 mySimpleArray
它指向了在堆上的四块内存区域。
对象数组
Simple* mySimpleArray = new Simple[4];delete[] mySimpleArray;mySimpleArray = nullptr;
指针数组,每个指针指向了一个对象。
const std::size_t s = 4;Simple** mySimpleArray1 = new Simple*[s];
for(int i = 0; i < s; ++i) {mySimpleArray1[i] = new Simple();
}
for(int i = 0; i < s; ++i)
{delete mySimpleArray1[i];mySimpleArray1[i] = nullptr;
}delete[] mySimpleArray1;
mySimpleArray1 = nullptr;
二维数组
char** board = new char*[size];char ** allocateCharacterBoard(size_t xDimension , size_t yDimension)
{char ** myArray = char* [xDimension];for(int i = 0; i < xDimension; ++i){myArray[i] = new char[yDimension];}return myArray;
}
数组指针二象性
数组会自动退化成指针。但并非所有的指针都是数组。
int *ptr = {new int};
内存泄漏
内存分配后没有及时释放
比如创建一个对象
Simple * originPoint = new Simple();
调用函数
void func(Simple * &outsimplePoint)
当你delete originPoint的时候,删除的是outsimplePoint而不是originPoint。
双重释放
当一个指针被释放之后。该指针关联的内存,就有可能在程序其他地方使用,现在它是一个悬空指针,当你再次释放该指针的时候,就有可能释放掉程序其他的地方的内存。
正确做法:应该在释放指针之后,将它置为空
智能指针
weak_ptr
1.weak_ptr 不拥有资源,用来确定关联的shared_ptr资源是否释放。
2.注意:
• 使用 weak_ptr 实例上的 lock() 成员函数,该函数返回一个 shared_ptr。如果在此期间关联的 shared_ptr 已经释放,返回的 shared_ptr 将为 nullptr。
• 创建一个新的 shared_ptr 实例,并将 weak_ptr 作为 shared_ptr 构造函数的参数传递。如果关联的 shared_ptr 已经释放,这将抛出 std::bad_weak_ptr 异常
要访问对象,必须将weak_ptr
转换为shared_ptr
(使用lock()
成员函数),如果对象还存在,则获得一个有效的shared_ptr
,否则返回空的shared_ptr
。
解决循环引用问题
class Child;
class Parent
{
public:std::shared_ptr<Child> child;~Parent();
};
class Child
{
public:std::shared_ptr<Parent> parent;~Child();
};int main()
{auto parent = std::make_shared<Parent>();auto child = std::make_shared<Child>();parent->child = child;child->parent = parent; // 循环引用
}// 只需要
class Child
{
public:std::weak_ptr<Parent> parent;~Child();
};
类和对象
拷贝赋值和移动赋值
Person& operator=(Person&& p)
Person& operator=(const Person& p) noexcept
拷贝赋值:把另一个对象的内容复制到 *this
,源对象保持不变(语义上)。
移动赋值:把另一个对象的资源窃取/接管到 *this
,源对象被置为“已搬走(moved-from)”的可用但未规定状态,从而避免昂贵的复制。
继承
静态绑定(早期绑定)
在C++中编译一个类时,会创建一个二进制对象,其中包含类的所有成员函数。函数非虚的时候,根据编译时类型,直接在调用成员函数地方硬编码。
虚函数表
有一个或多个虚函数的类都有一个虚函数表。该类的每个实例都包含一个指向该vtable的指针。
动态绑定(晚期绑定)
vtable包含指向虚成员函数实现的指针,当在指向或引用对象的指针或引用上调用成员函数时,会跟随其vtable指针,并根据运行时对象的实际类型调用对应的成员函数。
class Base
{public:virtual void func1();virtual void func2(); void test();
};class Derived : public Base
{public:void func1() override;void test();
};
虚析构函数
析构函数应该为虚。如果析构函数不为虚,很容易导致对象销毁时内存没有释放。只有在标记为 final 的类中,才可以让其析构函数设置为非虚。
```c++
class Base
{public:virtual ~Base();
};class Derived : public Base
{public:~Derived();
};
基类析构函数声明为虚函数,根据虚函数表,从派生类到基类的顺序一次调用析构函数。
如果不是,析构函数的选择编译时决定的。
构造顺序
基类->类的非静态数据成员->类
public Something
{public:Something() { std::cout << "2\n"; }
};class Base
{public:Base() { std::cout << "1\n"; }
};class Derived : public Base
{public:Derived() { std::cout << "3\n"; }private:Something _some;
};
结果:1,2,3
析构顺序
类->以与构造相反的顺序销毁类的数据成员->基类
上下转换
派生类转基类
会丢失自己所有的特性,只包含父类的特性。会产生切片。
Derived myDerived;
Base myBase { myDerived}; // 产生切片Base &myBase { myDerived}; // 不产生切片
基类转派生类
这是一种非常不好的设计。尽量使用 dynamic_cast()。
使用对象的内置类型来拒绝不合理的转换。这种内置类型位于vtable上,所以dynamic_cast只对具有vtable的对象有效。
在指针上转换失败,返回nullptr;
在对象引用上转换失败,抛出std::bad_cast异常;