让我们说,例如,我有一个类需要使用一些旧的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
命令,隐式定义的析构函数是否仍会处理它,或者我必须这样做?
另外,让我们解决我的另一个问题,因为它的关系非常密切。在上面的课程中,我是否需要明确定义析构函数才能在delete
上调用someString
,或者为我处理这个问题?
答案 0 :(得分:8)
您不仅需要定义析构函数来进行清理,还需要为类声明(并可选地定义)复制构造函数和复制赋值运算符,以确保正确复制副本。
隐式定义的析构函数会破坏成员变量。因此,例如,如果您有一个string
类型的成员变量,析构函数将自行销毁该变量。但是,指针的析构函数(如string*
)是一个无操作:你负责销毁指向的对象。
您还需要为此类定义复制操作,或者至少禁止生成编译器为您提供的默认复制操作。为什么?因为默认情况下,复制操作只是复制每个成员变量。所以,例如,如果您要写:
{
Foo x;
Foo y(x);
} // Uh oh
x
和y
都会在块的末尾被销毁。此时,x
和y
都指向相同的动态分配的互斥锁和字符串,因此互斥锁和字符串将被销毁两次(一次用于x
,一次用于y
})。
更好的选择是根本不使用手动内存分配。相反,你应该让someString
成为类的直接成员(即声明它string someString;
),或者你应该使用某种智能指针来管理它的生命周期(如unique_ptr
或{ {1}})。类似地,您应该使用带有自定义删除器的智能指针来管理互斥锁的生命周期,除非您的类是不可复制的,在这种情况下,您可以使互斥锁成为该类的直接成员。
答案 1 :(得分:3)
是的,你必须定义一个析构函数并销毁你的对象(someMutex和someString)。
但是,由于您已将someMutex
与malloc
分配,因此必须使用free
将其释放。
注意不要混合它们。
记住:
malloc
,已获得free
new
,已获得delete
new[]
,已获得delete[]
答案 2 :(得分:2)
我只是将string
的实例存储为数据成员(使用“堆栈语义”),而不是在类中存储指向string
的指针。
此外,我没有存储指向“原始”pthread_mutex_t
的指针,而是使用 RAII 定义一个C ++类来包装此pthread_mutex_t
资源(创建{ {1}}在其构造函数中,并在其析构函数中销毁它),然后我将此C ++类的实例存储为pthread_mutex_t
的数据成员。
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;
};
析构函数将自动调用每个数据成员析构函数,释放其资源。
通常,每个“原始”C资源(Foo
,pthread_mutex_t
等)都应该使用RAII包装在C ++类中,以及这些类的实例(就像它们有点“构建块“)应该用作其他类的数据成员。
这有助于简化代码(并编写异常安全的代码);如果使用此模式,则可以实现良好的代码安全性和可组合性。
答案 3 :(得分:1)
不,析构函数不应该删除这些数据(它可能是指向应用程序中其他位置分配的内存的指针)。所以你必须编写自己的析构函数。
还有一件事。使用malloc
分配内存后,您应该使用free()
释放内存。
答案 4 :(得分:0)
是否需要定义析构函数取决于当前对象是否为创建的对象提供OWNS,还是仅为其他要管理的对象创建它们。
使用malloc()
分配堆内存时,应使用free()
释放它。
使用new
创建对象时,必须使用delete
将其删除。
使用new[]
创建数组时,必须使用delete[]
删除它。
隐式析构函数会破坏成员变量,但在你的情况下,它们是指针,因此为指针本身分配的内存将被恢复,但不会分配你刚刚购买的内存。
另一种选择是使用“智能指针”(http://en.wikipedia.org/wiki/Smart_pointer),当删除当前对象(或超出范围)时,它将实际删除指向的对象。 / p>