举个例子,我有一个类需要使用一些旧的C东西(比如pthreads之类的),所以出于这样或那样的原因,我最终在构造函数中调用了malloc(),比如:
class Foo
{
public:
Foo()
{
someMutex = malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(someMutex);
someString = new string("yay string!");
}
private:
pthread_mutex_t * someMutex;
string * someString;
}
似乎有很多关于破坏者的错误信息。我不断看到显式定义的析构函数在基于指针的成员上调用delete
的例子,但我也不断读到,我不需要为类显式定义析构函数来管理内存;我只需要一个析构函数,比如文件句柄清理之类的。
因此引出了我的问题:即使someMutex
被分配了malloc
而不是C++new
命令,隐式定义的析构函数还会处理它吗?还是我必须这样做?
此外,让我们解决我的另一个问题,因为它是如此密切相关。在上面的类中,我需要显式定义一个析构函数才能在someString
上调用delete
吗?还是我自己负责?
您不仅需要定义一个析构函数来进行清理,还需要为类声明(并可选地定义)一个复制构造函数和复制赋值运算符,以确保正确地进行复制。
隐式定义的析构函数销毁成员变量。因此,例如,如果您有一个类型为string
的成员变量,则析构函数将单独销毁该变量。然而,指针的析构函数(如string*
)是一个非操作:您负责销毁指向的对象。
您还需要定义此类的复制操作,或者至少禁止生成编译器为您提供的默认复制操作。为什么?因为默认情况下,复制操作只复制每个成员变量。因此,例如,如果你要写:
{
Foo x;
Foo y(x);
} // Uh oh
CCD_ 9和CCD_。此时,x
和y
都指向同一个动态分配的互斥体和字符串,因此互斥体和串将被销毁两次(一次用于x
,一次用于y
)。
更好的选择是根本不使用手动内存分配。相反,您应该使someString
成为类的直接成员(即,声明它为string someString;
),或者您应该使用某种智能指针来管理它的生存期(如unique_ptr
或shared_ptr
)。类似地,您应该使用带有自定义deleter的智能指针来管理互斥体的生存期,除非您的类是不可压缩的,在这种情况下,您可以使互斥体成为该类的直接成员。
是的,您必须定义一个析构函数并销毁您的对象(someMutex和someString)。
但是,由于您已经用malloc
分配了someMutex
,因此必须用free
释放它。
注意不要把它们混在一起。
记住:
- 用
malloc
分配,用free
释放 - 用
new
分配,用delete
释放 - 用
new[]
分配,用delete[]
释放
与其在类中存储指向string
的指针,不如将string
的实例存储为数据成员(使用"堆栈语义")。
此外,我不存储指向"原始"pthread_mutex_t
的指针,而是定义一个C++类来使用RAII包装这个pthread_mutex_t
资源(在其构造函数中创建pthread_mutex_t
,并在其析构函数中销毁它),然后我将这个C++类的实例存储为Foo
的数据成员。
//
// C++ RAII wrapper on raw C pthread_mutex_t resource.
//
class PThreadMutex
{
public:
// Creates a pthread_mutex_t.
PThreadMutex()
{
pthread_mutex_init(&m_mutex, ...);
// Check for errors, and throw exceptions on errors
}
// Destroys a pthread_mutex_t
~PThreadMutex()
{
pthread_mutex_destroy(&m_mutex);
}
// Other member functions
// ...
// May define move constructor and move assignment operator for C++11
// ...
private:
pthread_mutex_t m_mutex;
};
class Foo
{
public:
Foo()
: m_someString("yay string!")
// m_someMutex initialized by its default constructor
{
}
~Foo()
{
// Nothing to do: C++ compiler will call the destructors
// of class data members, releasing their associated resources.
}
private:
//
// Class "building blocks":
//
PThreadMutex m_someMutex;
string m_someString;
};
通过这种方式,编译器为Foo
生成的析构函数将自动调用每个数据成员析构函数,释放它们的资源。
通常,每个"原始"C资源(pthread_mutex_t
、FILE *
等)都应该使用RAII封装在一个C++类中,并且这些类的实例(就像它们是一种"构建块"一样)应该用作其他类的数据成员。这有助于简化您的代码(以及编写异常安全代码);如果使用这种模式,您可以实现良好的代码安全性和可组合性。
不,析构函数不应该删除这些数据(它可能是指向应用程序中其他地方分配的内存的指针)。所以你必须编写自己的析构函数。
还有一件事。使用malloc
分配内存后,应该使用free()
释放内存。
是否需要定义析构函数取决于当前对象是拥有创建的对象,还是只为其他对象创建它们来管理。
当使用malloc()
分配堆内存时,应该使用free()
释放它。使用new
创建对象时,必须使用delete
将其删除。使用new[]
创建数组时,必须使用delete[]
将其删除。
隐式析构函数破坏成员变量,但在您的情况下,它们是指针,因此为指针分配的内存将被恢复,而不是您刚才malloc’ed分配的内存。
另一种选择是使用"智能指针"(http://en.wikipedia.org/wiki/Smart_pointer),当当前对象被删除(或超出范围)时,它实际上会删除指向的对象。