【问题标题】:C++ struct with dynamically allocated char arrays具有动态分配的 char 数组的 C++ 结构
【发布时间】:2020-10-03 06:04:55
【问题描述】:

我正在尝试将结构存储在向量中。 Struct 需要为给定大小的 char* 动态分配内存。 但是一旦我将结构添加到向量中,它的析构函数就会被调用,就好像我丢失了指向它的指针一样。

为了举例,我做了这个小演示。

#include "stdafx.h"
#include <iostream>
#include <vector>

struct Classroom
{
    char* chairs;

    Classroom() {} // default constructor

    Classroom(size_t size)
    {
        std::cout << "Creating " << size << " chairs in a classroom" << std::endl;
        chairs = new char[size];
    }

    ~Classroom()
    {
        std::cout << "Destroyng chairs in a classroom" << std::endl;
        delete[] chairs;
    }
};

std::vector<Classroom> m_classrooms;

int main()
{

    m_classrooms.push_back(Classroom(29));
    //m_classrooms.push_back(Classroom(30));
    //m_classrooms.push_back(Classroom(30));

    system("Pause");

    return 0;
}

输出是

Creating 29 chairs in a classroom
Destroyng chairs in a classroom
Press any key to continue . . .
Destroyng chairs in a classroom

是的,似乎析构函数被调用了两次!一次是添加到向量中,第二次是程序完成执行。

当我尝试使用类而不是结构时,会发生完全相同的事情。

谁能解释为什么会发生这种情况以及正确完成我的任务的可能方法是什么?

【问题讨论】:

  • 您需要使用char*吗? std::string 是字符串数据和 Just Works™ 管理其内存的正常选择。
  • 您需要提供一个复制构造函数,该构造函数实际上复制了chairs 成员指向的数据;否则,push_back 调用(调用默认副本)只会制作指针的副本。然后,当任何此类副本被销毁时,指针将失效。见en.wikipedia.org/wiki/…
  • @chris 我使用char* 因为我需要存储原始字节
  • std::vector&lt;char&gt;会更好。如果不需要使用char*,还有更好的方法。
  • @drescherjm 我认为std::vector&lt;char&gt; 没有用,因为无论如何都会调用析构函数,我会删除其中的向量元素

标签: c++ memory-management struct dynamic destructor


【解决方案1】:

Classroom 类不能安全地用于std::vector&lt;Classroom&gt;,因为它的复制语义不正确。 std::vector 将复制您的对象,如果复制语义有错误,那么当您开始在容器中使用该类时,您会看到所有这些错误,例如 vector

为了让您的类具有正确的复制语义,它需要能够无错误地构造、分配和销毁自身的副本(这些错误包括内存泄漏、对同一指针的双重删除调用等)

您的代码中缺少的另一件事是 size 参数需要在类中知道。现在,您发布的只是内存分配,但没有任何东西可以保存size。在不知道分配了多少个字符的情况下,用户定义的复制构造函数和赋值运算符的正确实现是不可能的,除非char * 是一个以空字符结尾的字符串。


话虽如此,有多种方法可以修复您的课程。最简单的方法是简单地使用具有正确复制语义的类型,而不是自己处理原始动态内存。这些类将包括std::vector&lt;char&gt;std::string。它们不仅自己清理,而且这些类知道自己的大小,而不必携带 size 成员变量。

struct Classroom
{
    std::vector<char> chairs;

    Classroom() {} // default constructor
    Classroom(size_t size) : chairs(size)
    {
        std::cout << "Creating " << size << " chairs in a classroom" << std::endl;
    }
};

上述类无需任何进一步调整即可工作,因为std::vector&lt;char&gt; 已经具有正确的复制语义。请注意,不再需要析构函数,因为std::vector 知道如何销毁自己。


如果由于某种原因您必须使用原始动态分配的内存,那么您的类必须实现用户定义的复制构造函数、赋值操作和析构函数。

#include <algorithm>

struct Classroom
{
    size_t m_size;
    char* chairs;

    // Note we initialize all the members here.  This was a bug in your original code
    Classroom() : m_size(0), chairs(nullptr) 
    {} 

    Classroom(size_t size) : m_size(size), chairs(new char[size])
    {}

    Classroom(const Classroom& cRoom) : m_size(cRoom.m_size),
                                        chairs(new char[cRoom.m_size]) 

    {
       std::copy(cRoom.chairs, cRoom.chairs + cRoom.m_size, chairs);
    }

    Classroom& operator=(const Classroom& cRoom)
    {
       if ( this != &cRoom )
       {
          Classroom temp(cRoom);
          std::swap(temp.m_size, m_size);
          std::swap(temp.chairs, chairs);
       }
       return *this;
   }

   ~Classroom() { delete [] chairs; }
};

在初始化类的成员时注意成员初始化列表的使用。在实现赋值运算符时还要注意copy / swap idiom 的用法。


另一个更正的问题是您的默认构造函数没有初始化所有成员。因此,在您的原始课程中,有一个简单的单行程序,例如:

int main()
{
   Classroom cr;
}

会引起问题,因为在析构函数中,您会删除未初始化的 chairs 指针。

此后,std::vector&lt;Classroom&gt; 现在应该可以安全使用了。

【讨论】:

【解决方案2】:

@LPVOID

使用 emplace_back(..) 就地创建对象可以帮助您避免在这里遇到的double free or corruption 错误。

m_classrooms.emplace_back(29)

但是,更好的做法是始终遵循 3/5/0 规则,以免最终出现悬空指针。

【讨论】:

    猜你喜欢
    • 2019-07-19
    • 2014-08-22
    • 1970-01-01
    • 2011-11-13
    • 1970-01-01
    • 1970-01-01
    • 2012-03-25
    • 2011-12-13
    相关资源
    最近更新 更多