在C ++中返回对象
当从一个类中返回对象时,什么时候释放内存?
例,
class AnimalLister { public: Animal* getNewAnimal() { Animal* animal1 = new Animal(); return animal1; } }
如果我创build动物列表的一个实例,并从它得到动物的参考,那么我应该删除它呢?
int main() { AnimalLister al; Animal *a1, *a2; a1 = al.getNewAnimal(); a2 = al.getNewAnimal(); }
这里的问题是AnimalLister没有办法跟踪创build的动物列表,所以我如何改变这种代码的逻辑来删除创build的对象。
根据您的使用情况,您可以在这里select几个选项:
-
每次创build动物时都要复制一份:
class AnimalLister { public: Animal getNewAnimal() { return Animal(); } }; int main() { AnimalLister al; Animal a1 = al.getNewAnimal(); Animal a2 = al.getNewAnimal(); }
优点:
- 容易明白。
- 不需要额外的库或支持代码。
缺点:
- 它要求
Animal
有一个行为良好的复制构造函数。 - 如果
Animal
比较复杂,可能会涉及很多复制,尽pipe返回值优化可以减轻很多情况。 - 如果你计划从
Animal
派生出来的子类,那么它们就不能工作,因为它们会被分割成一个普通的Animal
,从而失去了子类中的所有额外数据。
-
返回一个
shared_ptr<Animal>
:class AnimalLister { public: shared_ptr<Animal> getNewAnimal() { return new Animal(); } }; int main() { AnimalLister al; shared_ptr<Animal> a1 = al.getNewAnimal(); shared_ptr<Animal> a2 = al.getNewAnimal(); }
优点:
- 与对象层次结构一起工作(没有对象切片)。
- 不必复制大对象。
- 没有必要为
Animal
定义一个拷贝构造函数。
缺点:
- 需要Boost或TR1库或其他智能指针实现。
-
跟踪
AnimalLister
所有Animal
分配class AnimalLister { vector<Animal *> Animals; public: Animal *getNewAnimal() { Animals.push_back(NULL); Animals.back() = new Animal(); return Animals.back(); } ~AnimalLister() { for(vector<Animal *>::iterator iAnimal = Animals.begin(); iAnimal != Animals.end(); ++iAnimal) delete *iAnimal; } }; int main() { AnimalLister al; Animal *a1 = al.getNewAnimal(); Animal *a2 = al.getNewAnimal(); } // All the animals get deleted when al goes out of scope.
优点:
- 理想的情况下,你需要一堆
Animal
的时间有限,并计划一次释放他们。 - 轻松适应自定义内存池,并释放所有的
Animal
在一个单一的delete
。 - 与对象层次结构一起工作(没有对象切片)。
- 不必复制大对象。
- 没有必要为
Animal
定义一个拷贝构造函数。 - 不需要外部库。
缺点:
- 上面写的实现不是线程安全的
- 需要额外的支持代码
- 比前两个scheme不太清楚
- 当AnimalLister超出范围的时候,它将会把动物带走。 你不能再挂在动物列表上了。
- 理想的情况下,你需要一堆
我build议返回一个std::tr1::shared_ptr
(或boost::shared_ptr
,如果您的C ++实现没有TR1)而不是原始指针。 所以,而不是使用Animal*
,而是使用std::tr1::shared_ptr<Animal>
。
共享指针为您处理引用跟踪,如果没有引用,则自动删除对象。
最简单的方法是返回智能指针,而不是常规指针。 例如:
std::auto_ptr< Animal> getNewAnimal() { std::auto_ptr< Animal > animal1( new Animal() ); return animal1; }
如果您可以使用TR1或Boost,也可以使用shared_ptr <>。
指针和分配内存的经典问题。 这是关于责任 – 谁负责清理由AnimalLister对象分配的内存。
你可以在AnimalLister本身存储一个指向动物分配的每个动物的指针,并将其清理干净。
但是,你在main()中有一些指向坐在那里的动物的指针,它会引用被删除的内存。
我认为参考计数解决scheme比滚动您自己的解决scheme更好的原因之一。
- shared_ptr(运行良好),
- 返回一个简单的指针,告诉你的类的用户现在是他们的动物,他们有责任在完成时删除它,
-
实现一个“自由动物(动物*)”的方法,很明显,删除动物指针是必需的。
-
另一种方法是直接返回动物对象,没有指针,没有呼叫新的。 复制构造函数将确保调用者获得他们自己的动物对象,他们可以将其存储在堆或栈中,或者根据需要复制到容器中。
所以:
class AnimalLister { Animal getAnimal() { Animal a; return a; }; // uses fast Return Value Optimisation }; Animal myownanimal = AnimalLister.getAnimal(); // copy ctors into your Animal object
RVO意味着返回对象而不是指针实际上更快(因为编译器不会创build新对象并将其复制到调用者的对象中,而是直接使用调用者的对象)。
在Scott Meyers的深入讨论中 ,他总结说使用shared_ptr或auto_ptr是最好的。
或者你可以按照COM-ISH的方法,并应用简单的引用计数。
- 创build对象时,立即给它一个参考值1
- 当任何人得到指针的副本时,他们AddRef()
- 当任何人放弃指针的副本时,他们释放()
如果引用计数达到0,则该对象将自行删除。
它最终是shared_ptr在底层做了什么,但是它让你更好地控制正在发生的事情,并且以我的经验更容易debugging。 (它也是非常跨平台的)。
我还没有给我一个机会,所以可以完美地为你的目的服务。
释放对象占用的内存的时间是当你不再需要这个特定对象的时候。 在您的具体情况下,AnimalLister类的用户请求一个指向Animal类的新分配对象的指针。 所以,当他需要那个指针/对象的时候,他是负责释放内存的人。
AnimalLister lister; Animal* a = lister.getNewAnimal(); a->sayMeow(); delete a;
在我看来,在这种情况下,没有必要过度devise任何东西。 AnimalLister只是一个创build新的动物对象的工厂,就是这样。
我真的很喜欢乔希的回答,但是我想我可能会因为还没有列出来而换个模式。 这个想法是强制客户端代码来处理跟踪动物。
class Animal { ... private: //only let the lister create or delete animals. Animal() { ... } ~Animal() { ... } friend class AnimalLister; ... } class AnimalLister { static s_count = 0; public: ~AnimalLister() { ASSERT(s_count == 0); } //warn if all animals didn't get cleaned up Animal* NewAnimal() { ++count; return new Animal(); } void FreeAnimal(Animal* a) { delete a; --s_count; } }