C++ 语言将内存管理留给了程序员,这就是你会发现这种混乱程度的原因。
重复 Luchian Grigore 所说的三种主要类型的记忆
如果您在自动存储中分配对象,则一旦范围终止,该对象将被销毁;例如
void foo() {
MyClass myclass_instance;
myclass_instance.doSomething();
}
在上述情况下,函数终止时myclass_instance
会自动销毁。
如果您改为在堆中分配一个对象,new
那么您有责任使用 调用析构函数delete
。
在 C++ 中,对象也可以有子对象。例如:
class MyBiggerClass {
MyClass x1;
MyClass x2;
...
};
这些子对象分配在包含对象分配到的同一内存中
void foo() {
MyBiggerClass big_instance;
MyBiggerClass *p = new MyBiggerClass();
...
delete p;
}
在上述情况下,两个子对象big_instance.x1
和big_instance.x2
将被分配在自动存储(堆栈)中,而p->x1
和p->x2
被分配在堆上。
但是请注意,在这种情况下,您不需要调用delete p->x1;
(编译错误,p->x1
不是指针) 或delete &(p->x1);
(语法上有效,但逻辑错误,因为它没有在堆上显式分配,而是作为另一个的子对象目的)。只需删除主要对象p
即可。
另一个复杂之处是一个对象可能会保留指向其他对象的指针,而不是直接包含它们:
class MyOtherBigClass {
MyClass *px1;
MyClass *px2;
};
在这种情况下,它的构造函数MyOtherBigClass
必须为子对象找到内存,并且~MyOtherBigClass
必须负责销毁子对象并释放内存。
在 C++ 中,销毁原始指针不会自动销毁内容。
简单情况下的基类可以看作是隐藏的嵌入子对象。即,就像基础对象的实例嵌入派生对象中一样。
class MyBaseClass {
...
};
class MyDerivedClass : MyBaseClass {
MyBaseClass __base__; // <== just for explanation of how it works: the base
// sub-object is already present, you don't
// need to declare it and it's a sub-object that
// has no name. In the C++ standard you can find
// this hidden sub-object referenced quite often.
...
};
这意味着派生对象的析构函数不需要调用基对象的析构函数,因为这是由语言自动处理的。虚拟基的情况更复杂,但基析构函数的调用仍然是自动的。
鉴于内存管理由程序员控制,因此出现了一些策略来帮助程序员避免编写总是导致对象泄漏或多次破坏的复杂代码。
仔细规划您将如何处理实例的生命周期。你不能把它作为事后的想法,因为以后不可能修复。对于每个对象实例,应该清楚谁创建和谁销毁它。
当无法提前计划何时应该销毁对象时,请使用引用计数器:对于每个对象,跟踪有多少指针正在引用它,并在该数字达到零时销毁该对象。有一些智能指针可以为您解决这个问题。
永远不要保留一个指向已经被销毁的对象的指针。
使用明确设计的类来处理包含对象的生命周期的容器。例子是std::vector
或std::map
。