我正在阅读C++ Primer Plus by Stephen Frata。我已经阅读了第6章,这意味着我学习了指针而不是关于对象和类(虽然我知道OOP)。
我来自ActionScript(Flash)和Java背景,所以我之前从未处理过指针,但我理解它们。不过,我有很多关于他们的问题。
据我所知,你需要配对new和delete,即创建指针的对象/函数负责释放它。但想象一下简单的工厂功能,如:
SomeObject * createSomeObject(){
return new SomeObject;
}
这看起来很成问题。谁负责现在释放这个指针?
如果我创建一个允许公共访问其创建的指针的类,该怎么办?遵循new / delete规则,此类应负责释放析构函数中的指针。但由于指针可能被另一个类使用,因此销毁第一个类会破坏第二个......
这两个审讯是相似的。如何管理除创建指针以外的其他实体已知的指针?
注意:我知道智能指针可以解决这个问题,但我想知道如果没有它们,人们会怎么做。
答案 0 :(得分:3)
“谁负责删除它?”是一个非常好的问题。通常使用这样的函数,您只需记录必须删除返回的指针。由工厂的用户决定哪个类或功能负责。但是,这有点模糊,确实是一个问题。
在现代C ++风格中,这正是使用智能指针的原因。考虑:
std::unique_ptr<SomeObject> createSomeObject() {
return new SomeObject;
}
在这种情况下,指针归返回的unique_ptr
所有。无论您何时移动它,存储的指针都会在其析构函数中被删除(当它在范围外或包含它的对象析出时)。这显然代码的哪一部分负责销毁它并且删除自动发生(所以你不能忘记删除它或进行一些“破坏”调用),因此被认为是上述问题的解决方案。
答案 1 :(得分:2)
内存管理问题,“所有权”和对象的生命周期严重影响了C ++中的设计。通常,智能指针和类似技术是首选。
但是,如果您不想使用智能指针等,那么您必须非常严谨。通常,特定对象的内存管理应该跨一个接口发生。因此,创建基于堆的对象(例如createSomeObject()
)的任何函数都应该具有删除对象的匹配函数(例如deleteSomeObject(SomeObject *)
)。当然,这类指南总是有例外。
这个以及良好的文档可以最大限度地减少某人搞砸并导致内存泄漏的可能性。
答案 2 :(得分:2)
有不同的方法:
一个是“不在堆上分配:
SomeObject createSomeObject(){
return SomeObject();
}
如果你这样做,没有人必须释放该对象,你没有任何担心的指针。 潜在的缺点是SomeObject
必须是可复制的,但通常这是一个很好的解决方案,它通常应该是您的默认值。不要在用户代码中使用new
/ delete
,将它们隐藏在构造函数/析构函数调用中。 (例如,也许SomeObject
在内部分配堆上的一些数据,并在对象本身被销毁时释放它。)
第二种方法是相关的,但使用智能指针:
std::shared_ptr<SomeObject> createSomeObject(){
return std::make_shared(new SomeObject());
}
这类似于你没有返回一个指针,你正在返回一个对象,它负责删除任何需要删除的东西。智能指针已取得SomeObject
实例的所有权,并在适当时删除它。
根据具体情况,std::auto_ptr
或std::unique_ptr
可能更合适。
在这两种情况下,你都依赖RAII,这是每个C ++程序员都应该知道的非常强大的习惯用语。资源应始终包装在本地(非堆分配)对象中,这些对象会根据需要进行复制和移动,并负责清理其内部资源。
答案 3 :(得分:1)
这看起来很成问题。谁是 负责释放这个指针 现在?
我建议配对功能。
Xyz* CreateXyz();
void DestroyXyz(Xyz *xyz);
Abc* NewAbc();
void DeleteAbc(Abc *abc);
或者您只需将责任转移删除Xyz/Abc
到客户端,即调用该函数的人在使用后也必须对返回的对象进行删除。
无论您选择什么,请在文档中明确说明如何销毁创建的对象。
我更喜欢配对功能,尤其是在删除之前要考虑很多事情的时候!
编辑:当您构建DLL或某个动态库时,我建议使用 pair-functions 方法。实际上,这确保了对象从创建它的相同内存池中销毁!
答案 4 :(得分:0)
没有人,who is responsible for creating/destroying.
您定义逻辑。
如果您确实将指针分配给512字节以存储128-char长字符串,则成功更改了字符串并将其保存到文件中,您可以在此之后随时销毁指针。
答案 5 :(得分:0)
正如你所说,在C ++中并不总是如此“创建指针的对象/函数负责释放它”。在类工厂中,通常使用该对象的代码将删除它,如果您可以确保没有指针的其他用户。否则,需要智能指针或一些引用计数方案。
答案 6 :(得分:0)
通常,工厂函数不负责删除它创建的对象。如果你将new / delete all的指南强加于一个类中,那么返回几乎是不可能的 指针 - 特别是在多线程环境中。
在大多数情况下,文档/引用明确指定谁负责删除对象(如果不明显)。没有手动处理这个问题的可能解决方案会进入我认为的垃圾收集领域(参考计数器是一种流行的方法)。
答案 7 :(得分:0)
对于在任何情况下都有效的方法,没有任何简单的答案。规则总是有例外。您只需要考虑不同的软件架构,并决定哪一个对于阅读代码的人来说最容易理解。
答案 8 :(得分:0)
如果有新的&amp; amp;基本上不可能在C ++中编写异常安全代码。删除。
所以,你基本上有两个选择:
基本思想是,在框架中创建一组复制成本低廉的类,它们包含另一个包含引用计数的类和一个复制成本高昂的资源。
通过在廉价复制类上提供赋值和复制操作符,您将获得一个可以使用与原始类型相同的语义传递的数据结构,如int。
它只不过是智能指针的重新发明,但我喜欢它,因为语法比在代码中使用非typedef的智能指针更简洁,并且对于充分了解c ++构造函数和运算符重载以写入包装器。
答案 9 :(得分:0)
Jalf已经提到了重要的一点,即你不必在堆上分配。
其他的东西也会发挥作用,这本书可能不会告诉你这个:
C ++在OOP上并不是特别擅长。尽管最初设计为带有类的C(至少暗示了它的OOP目标),但OOP在C ++中实际上非常麻烦,而不是大多数现代C ++习语中的焦点。
如果你绑定到OPP框架,你基本上有两个选择:
通常,您尝试在C ++中没有显式指针和freestore管理。也就是说,您在堆栈上分配业务对象,并通过RAII将所有动态内存管理封装到它们中。
当然,这使得亚型多态性更难实现。但为了缓解这种情况,C ++提供了一种非常强大的模板机制,可以让您编写极其抽象的高级代码(可以说比OOP更多)。这有时被称为面向算法,以使其偏离面向对象。 C ++ <algorithms>
标头和标准库容器就是一个很好的例子。