析构函数和Malloc'd成员

时间:2012-01-18 00:00:50

标签: c++ malloc destructor new-operator delete-operator

让我们说,例如,我有一个类需要使用一些旧的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,或者为我处理这个问题?

5 个答案:

答案 0 :(得分:8)

您不仅需要定义析构函数来进行清理,还需要为类声明(并可选地定义)复制构造函数和复制赋值运算符,以确保正确复制副本。

隐式定义的析构函数会破坏成员变量。因此,例如,如果您有一个string类型的成员变量,析构函数将自行销毁该变量。但是,指针的析构函数(如string*)是一个无操作:你负责销毁指向的对象。

您还需要为此类定义复制操作,或者至少禁止生成编译器为您提供的默认复制操作。为什么?因为默认情况下,复制操作只是复制每个成员变量。所以,例如,如果您要写:

{
    Foo x;
    Foo y(x);
}   // Uh oh

xy都会在块的末尾被销毁。此时,xy都指向相同的动态分配的互斥锁和字符串,因此互斥锁和字符串将被销毁两次(一次用于x,一次用于y })。


更好的选择是根本不使用手动内存分配。相反,你应该让someString成为类的直接成员(即声明它string someString;),或者你应该使用某种智能指针来管理它的生命周期(如unique_ptr或{ {1}})。类似地,您应该使用带有自定义删除器的智能指针来管理互斥锁的生命周期,除非您的类是不可复制的,在这种情况下,您可以使互斥锁成为该类的直接成员。

答案 1 :(得分:3)

是的,你必须定义一个析构函数并销毁你的对象(someMutex和someString)。

但是,由于您已将someMutexmalloc分配,因此必须使用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资源(Foopthread_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>