我一直在为这个问题磕磕绊绊。我试图返回指向对象的指针,我可以说
MyObject* obj = Manager::Create(int i, int j);
如何正确分配内存以避免泄漏?我以为我应该调用new来在堆上记忆,但最近我被告知了。
答案 0 :(得分:4)
“我一直在为这个问题磕磕绊绊。我试图返回指向对象的指针,我可以说
MyObject* obj = Manager::Create(int i, int j);
如何正确分配内存以避免泄漏?我以为我应该打电话给新记录,但我最近却被告知了。“
由于你在询问如何分配内存,并且因为这发生在Manager::Create
内,我能看到的唯一合理解释是你是那个写Manager::Create
函数的人。
所以,首先,你真的需要一个工厂,“manager”实际管理什么(如果有的话)?
我的印象是,来自Java背景的人们在添加不必要的动态分配和工厂以及“管理者”,单身人士和信封模式等方面具有强大的倾向性,这些在C ++中通常是不合适的。
别。
例如,如果您的obj
仅在本地范围内需要, 使用自动存储 (基于堆栈的分配和解除分配),这可以是订单比类似Java的动态分配更有效:
MyObject obj( i, j );
这是适用的,那么问题“我如何正确分配内存以便没有泄漏”在C ++中有一个非常简单的答案: 只是声明变量 ,如上所述。
即使您必须从函数返回此类对象,这也适用。然后只需 按值返回 (显然是复制对象)。例如,与下面的foo::reduce
函数一样,
#include <iostream> // std::cout, std::endl
#include <string> // std::string, std::to_string
namespace foo {
using namespace std;
class MyObject
{
private:
string description_;
public:
string description() const { return description_; }
MyObject( int const x, int const y )
: description_( "(" + to_string( x + 0LL ) + ", " + to_string( y + 0LL ) + ")" )
{}
};
ostream& operator<<( ostream& stream, MyObject const& o )
{
return stream << o.description();
}
int gcd( int a, int b )
{
return (b == 0? a : gcd( b, a % b ));
}
MyObject reduce( int const a, int const b )
{
int const gcd_ab = gcd( a, b );
return MyObject( a/gcd_ab, b/gcd_ab );
}
} // namespace foo
int main()
{
using namespace foo;
int const a = 42;
int const b = 36;
cout << MyObject( a, b ) << " -> " << reduce( a, b ) << endl;
}
现在让我们看看如何通过引入不必要的动态分配来使这种简洁,简单和高效的代码变得冗长,复杂和低效。我写“不必要”,因为标准C ++库的容器等和C ++语言的设施已经避免了动态分配的大多数传统原因。例如,以前您可能已经使用过指针以避免代价高昂的复制,并因此遇到“我需要清理,但是对象是在自动存储上分配还是动态?”的问题,使用C ++ 03可以使用智能诸如boost::shared_ptr
之类的指针,无论对象的来源如何,都可以自动进行适当的破坏,而使用C ++ 11,您可以使用移动语义来大大避免复制效率低下,从而首先不会弹出问题。 / p>
因此,在下面的代码中使用动态分配,使用现代C ++,非常 artifical 和解释;它没有任何实际优势。
但是通过这种动态分配,即使对于这个例子它是人为的并且被解释,必须保证适当的解除分配,并且通常的方法是 使用智能指针 < / strong>例如std::unique_ptr
:
#include <iostream> // std::cout, std::endl
#include <memory> // std::unique_ptr, std::default_delete
#include <string> // std::string, std::to_string
namespace foo {
using namespace std;
class MyObject
{
friend struct default_delete<MyObject>;
private:
string description_;
protected:
virtual ~MyObject() {} // Restrics class to dynamic allocation only.
public:
typedef unique_ptr<MyObject> Ptr;
string description() const { return description_; }
MyObject( int const x, int const y )
: description_( "(" + to_string( x + 0LL ) + ", " + to_string( y + 0LL ) + ")" )
{}
};
ostream& operator<<( ostream& stream, MyObject const& obj )
{
return stream << obj.description();
}
int gcd( int a, int b )
{
return (b == 0? a : gcd( b, a % b ));
}
MyObject::Ptr reduce( int const a, int const b )
{
int const gcd_ab = gcd( a, b );
return MyObject::Ptr( new MyObject( a/gcd_ab, b/gcd_ab ) );
}
} // namespace foo
int main()
{
using namespace foo;
int const a = 42;
int const b = 36;
MyObject::Ptr const pData( new MyObject( a, b ) );
MyObject::Ptr const pResult( reduce( a, b ) );
cout << *pData << " -> " << *pResult << endl;
}
注意protected
析构函数和调用析构函数的代码的friend
声明。 protected
析构函数确保不能创建静态或自动实例,只能使用动态分配(例如,可以强制实施此限制,以便更容易实现类并确保没有自动对象链接到动态数据结构中)。 friend
声明使protected
析构函数可以被标准库的对象销毁函数访问 - 但遗憾的是,unique_ptr
只使用了该函数。
我提到并举例说明这是因为你有一个工厂函数,它有时用于将类限制为动态分配(特别是来自Java的人)。出于此目的,工厂函数是不合适的,因为使用 n 构造函数,您需要 n 工厂函数。相反,使用protected
析构函数,您只需要一个公共的删除函数,如上所示,这是由标准库提供的。
所以,结果,通常是“如何正确分配内存以便没有泄漏”这一问题的答案是 将解除分配责任委托给语言或标准库 ,或其他库组件。首先,这意味着使用自动存储(局部变量)和值返回。但是当有需要时,它还包括使用标准库集合类,例如std::vector
和std::map
。只有在那些不提供所需功能的情况下,才考虑动态分配。然后将解除分配责任委托给std::unique_ptr
和std::shared_ptr
等智能指针。
答案 1 :(得分:2)
通常您会返回std::unique_ptr<MyObject>
,并执行以下操作:
return std::unique_ptr<MyObject>(Manager::Create(i, j), &Manager::Free);
或者您是否正在尝试自己撰写Manager::Create
?如果是这样的话:
std::unique_ptr<MyObject> Manager::Create(int i, int j)
{
return std::unique_ptr<MyObject>(new MyObject(i, j)); // default deleter is appropriate
}
答案 2 :(得分:2)
嗯......我觉得你有点困惑。是的,您可以调用我以为我应该打电话给新人来堆积内存,但最近我被告知了。
new
来动态分配内存。然而,有一些常见的模式(参见; RAII)用于不惜一切代价避免它,因为它是一种轻松的方式射击自己(阅读;写错误)。
在某些时候, somethign 必须调用new
才能动态分配内存。首先,你需要问问自己;这是否需要动态分配?如果答案是否定的,那么就这样声明并继续前进。
void Foo() {
MyObject obj; // automatic storage space, will be cleaned up when the scope is left
}
接下来,为什么不将指针存储在std::unique_ptr
或类似的东西中?这将照顾你的管理,再次,你不管理记忆;
// calls delete in the destructor
std::unique_ptr pointer( new MyObject() );
BOOST库有一个等价的类(unique_ptr
是C ++ 11)。
关键是内存是由类通过其构造函数和析构函数来管理的。您在创建对象时动态分配内存,并在其析构函数中释放它(即,调用delete
)。您只需在尽可能紧的范围内堆叠分配这些内容,您就不必担心内存泄漏。
我每天都在写C ++,而且我几乎从不致电new
或delete
。作为一个小例子,让我们来看看这个类,这是一个范围指针的简单实现(注意;这不是一个“正确的”实现,而且过于简单!这个问题比这更难解决,但我只是单独使用它作为管理动态分配对象的生命周期的一个例子。)
template<class T>
class ScopedPointer {
public:
ScopedPointer(T* obj) {
m_pointer = obj;
}
~ScopedPointer() {
delete m_pointer;
}
inline T* operator->()
{
return( m_pointer );
}
inline bool IsValid() const {
return m_pointer != NULL;
}
private:
T* m_pointer;
};
您可以将此类用作指向动态分配内存的指针的包装器。当它离开它的范围时,它的析构函数将被调用并且内存将被清除。同样,这不是生产质量代码!这是不正确的,因为它缺少真实世界类需要的几种机制(复制/所有权语义,更先进的解除分配器等)。
void Foo() {
ScopedPointer<MyObject> ptr( new MyObject() );
ptr->whatever();
} // destructor is called, dynamic memory is freed