通过旧的C ++期刊阅读,我注意到了一些事情。
其中一篇文章断言
Foo *f = new Foo();
几乎是不可接受的专业C ++代码,并且自动内存管理解决方案是合适的。
是这样吗?
编辑:改述:直接内存管理对于新的C ++代码是不可接受的,在一般中? auto_ptr(或其他管理包装器)应该用于大多数新代码吗?
答案 0 :(得分:19)
这个例子非常像Java 在C ++中,如果需要,我们只使用动态内存管理 一个更好的选择就是声明一个局部变量。
{
Foo f;
// use f
} // f goes out of scope and is immediately destroyed here.
如果必须使用动态内存,请使用智能指针。
// In C++14
{
std::unique_ptr<Foo> f = std::make_unique<Foo>(); // no need for new anymore
}
// In C++11
{
std::unique_ptr<Foo> f(new Foo); // See Description below.
}
// In C++03
{
std::auto_ptr<Foo> f(new Foo); // the smart pointer f owns the pointer.
// At some point f may give up ownership to another
// object. If not then f will automatically delete
// the pointer when it goes out of scope..
}
有一大堆os智能指针提供给int std ::和boost ::(现在有些在std :: tr1中)选择合适的一个并用它来管理对象的生命周期。
请参阅Smart Pointers: Or who owns you baby?
从技术上讲,您可以使用new / delete进行内存管理 但在真正的C ++代码中,它几乎从未完成。手动进行内存管理几乎总有一种更好的选择。
一个简单的例子是std :: vector。在封面下它使用new和delete。但是你永远无法从外面说出来。这对班级用户完全透明。用户知道的所有内容都是向量将获取对象的所有权,并且在向量被销毁时它将被销毁。
答案 1 :(得分:8)
我认为,所有这些“...最佳实践......”问题的问题在于,他们都在没有上下文的情况下考虑代码。如果你问“一般”,我必须承认直接内存管理是完全可以接受的。它在语法上是合法的,并且不违反任何语言语义。
对于替代方案(堆栈变量,智能指针等),它们都有其缺点。而且他们都没有灵活性,直接内存管理也有。您需要为这种灵活性支付的价格是您的调试时间,您应该了解所有风险。
答案 2 :(得分:7)
没有
在某些情况下,有充分的理由不使用自动内存管理系统。 这些可能是由于周期性参考等导致的性能,数据结构的复杂性等。
但是,如果你有充分的理由不使用更聪明的东西,我建议只使用带有new / malloc的原始poiner。看到无保护的分配让我害怕,让我希望编码员知道他们在做什么。
像boost :: shared_ptr这样的智能指针类,boost :: scoped_ptr将是一个好的开始。 (这些将是C ++ 0x标准的一部分,所以不要害怕它们;))
答案 3 :(得分:6)
使用某种智能指针方案,您可以获得自动内存管理,引用计数等,只需很少的开销。你为此付出了代价(内存或性能),但为它支付费用可能是值得的,而不是一直担心它。
答案 4 :(得分:6)
如果您使用的是异常,那么实际上可以保证某种代码会导致资源泄漏。即使您禁用了异常,在手动将新配置与删除配对时也很容易进行清理。
答案 5 :(得分:4)
这完全取决于我们的意思。
new
来分配内存吗?当然应该,我们别无选择。 new
是 在C ++中动态分配对象的方法。当我们需要动态分配类型为T的对象时,我们会new T(...)
。new
调用 吗? 否即可。在java或C#中,new
用于创建新对象,因此您可以在任何地方使用它。在C ++中,它仅用于堆分配。几乎所有对象都应该进行堆栈分配(或作为类成员就地创建),以便语言的作用域规则帮助我们管理它们的生命周期。 new
通常不是必需的。通常,当我们想要在堆上分配新对象时,您可以将其作为较大集合的一部分进行操作,在这种情况下,您应该将对象推送到STL容器中,并让它担心分配和释放内存。如果您只需要一个对象,通常可以将其创建为类成员或局部变量,而不使用new
。new
?很少,如果有的话。如上所述,它通常可以并且通常应该隐藏在包装类中。例如std::vector
动态分配所需的内存。因此vector
的用户无需关心。我只是在堆栈上创建一个向量,它为我处理堆分配。当向量或其他容器类不合适时,我们可能想要编写自己的RAII包装器,它在构造函数中使用new
分配一些内存,并在析构函数中释放它。然后该包装器可以进行堆栈分配,因此该类的用户永远不必调用new
。其中一篇文章声称
Foo *f = new Foo();
几乎是不可接受的专业C ++代码,并且自动内存管理解决方案是合适的。
如果他们的意思是我认为他们的意思,那么他们是对的。正如我上面所说,new
通常应隐藏在包装类中,其中自动内存管理(以作用域生命周期的形式和在超出范围时调用其析构函数的对象)可以为您处理它。本文没有说“永远不会在堆上分配任何东西”或者从不使用new
“,而只是”当你使用new
时,不要只存储指向已分配内存的指针。将它放在某种类中,当它超出范围时可以将它释放出来。
而不是Foo *f = new Foo();
,您应该使用其中之一:
Scoped_Foo f; // just create a wrapper which *internally* allocates what it needs on the heap and frees it when it goes out of scope
shared_ptr<Foo> f = new Foo(); // if you *do* need to dynamically allocate an object, place the resulting pointer inside a smart pointer of some sort. Depending on circumstances, scoped_ptr, or auto_ptr may be preferable. Or in C++0x, unique_ptr
std::vector<Foo> v; v.push_back(Foo()); // place the object in a vector or another container, and let that worry about memory allocations.
答案 6 :(得分:2)
我不久前停止编写这样的代码。有几种选择:
基于范围的删除
{
Foo foo;
// done with foo, release
}
scoped_ptr用于基于范围的动态分配
{
scoped_ptr<Foo> foo( new Foo() );
// done with foo, release
}
shared_ptr表示应该在很多地方处理的事情
shared_ptr<Foo> foo;
{
foo.reset( new Foo() );
}
// still alive
shared_ptr<Foo> bar = foo; // pointer copy
...
foo.reset(); // Foo still lives via bar
bar.reset(); // released
基于工厂的资源管理
Foo* foo = fooFactory.build();
...
fooFactory.release( foo ); // or it will be
// automatically released
// on factory destruction
答案 7 :(得分:1)
一般来说,没有,但一般情况并非常见。这就是为什么像RAII这样的自动方案首先被发明的原因。
从我写的答案到另一个问题:
程序员的工作就是表达 用他的语言优雅的东西 选择。
C ++具有非常好的语义 建设和破坏 堆栈上的对象。如果是资源 可以在a的持续时间内分配 范围块,然后一个优秀的程序员 可能会走最少的路 抵抗性。对象的生命周期是 由大括号划分,可能是 反正已经存在了。
如果没有好办法的话 对象直接在堆栈上,也许吧 可以放在另一个对象中作为 会员。现在它的寿命有点 更长,但C ++仍然很多 自动。对象的生命周期 由父对象分隔 - 问题已被委派。
但可能没有一位家长。 接下来最好的事情是一系列的 领养父母。这是什么
auto_ptr
是为了。还不错, 因为程序员应该知道 什么特定的父母是所有者。 对象的生命周期由分隔 它的序列的生命周期 拥有者。迈进了一步 决定论和本身的优雅是shared_ptr
:生命分隔 所有者联盟。<强>&GT;但也许这个资源不是 与任何其他对象并发设置 对象,或控制流程 系统。它是在某些事件中创建的 发生并摧毁另一个人 事件。虽然有很多 用于划分生命周期的工具 代表团和其他生命,他们 不足以计算任何 任意功能。所以程序员 可能决定写一个函数 几个变量来判断是否 一个物体正在形成或存在 消失,并致电
new
和delete
。强>最后,编写函数可以 硬。也许管理的规则 对象需要花费太多时间 内存实际计算!它 可能只是很难表达 他们优雅地回到我的身边 原点。所以我们有 垃圾收集:对象 生命是由你想要的时间界定的 它,当你没有。
答案 8 :(得分:0)
首先,我认为它应该是Foo *f = new Foo();
我之所以不喜欢使用这种语法,是因为很容易忘记在代码末尾添加delete
并让你的记忆保持畅通。
答案 9 :(得分:0)
通常,您的示例不是异常安全的,因此不应使用。如果该线直接跟随新抛出?堆栈展开,你刚刚泄露内存。作为堆栈展开的一部分,智能指针将为您处理它。如果您倾向于不处理异常,那么在RAII问题之外没有任何退缩。