析构函数和 Malloc'd 成员



举个例子,我有一个类需要使用一些旧的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_。此时,xy都指向同一个动态分配的互斥体和字符串,因此互斥体和串将被销毁两次(一次用于x,一次用于y)。


更好的选择是根本不使用手动内存分配。相反,您应该使someString成为类的直接成员(即,声明它为string someString;),或者您应该使用某种智能指针来管理它的生存期(如unique_ptrshared_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_tFILE *等)都应该使用RAII封装在一个C++类中,并且这些类的实例(就像它们是一种"构建块"一样)应该用作其他类的数据成员。这有助于简化您的代码(以及编写异常安全代码);如果使用这种模式,您可以实现良好的代码安全性和可组合性。

不,析构函数不应该删除这些数据(它可能是指向应用程序中其他地方分配的内存的指针)。所以你必须编写自己的析构函数。

还有一件事。使用malloc分配内存后,应该使用free()释放内存。

是否需要定义析构函数取决于当前对象是拥有创建的对象,还是只为其他对象创建它们来管理。

当使用malloc()分配堆内存时,应该使用free()释放它。使用new创建对象时,必须使用delete将其删除。使用new[]创建数组时,必须使用delete[]将其删除。

隐式析构函数破坏成员变量,但在您的情况下,它们是指针,因此为指针分配的内存将被恢复,而不是您刚才malloc’ed分配的内存。

另一种选择是使用"智能指针"(http://en.wikipedia.org/wiki/Smart_pointer),当当前对象被删除(或超出范围)时,它实际上会删除指向的对象。

相关内容

  • 没有找到相关文章