C++动态内存

C++ dynamic memory

本文关键字:内存 动态 C++      更新时间:2023-10-16

问题是:在这个函数的末尾,"tasks[taskCount]"中的元素成员,如名称、截止日期等,确实是传递给这个函数的内容,但是在返回这个函数的调用者之后,所有这些值都变成了垃圾,除了任务计数,它不是动态的。此函数在类"任务列表"范围内定义

void addTask(char name[],char course[],char dueDate[]){
    taskCount++;
    Task task(taskCount, name, course, dueDate);
    tasks[taskCount] = task;
}

以下是类"任务"的简要定义:

class Task
{
private:
    int number;
    char* name;
    char* dueDate;
    char* course;
public:
    Task(){
        name = new char[TEXT_SIZE + 1];
        dueDate = new char[TEXT_SIZE + 1];
        course = new char[TEXT_SIZE + 1];
        saveText = new char[(TEXT_SIZE * 3) + 1];
    };
    Task(int num, char n[], char c[], char d[]){
        number = num;
        name = new char[strlen(n) + 1];
        dueDate = new char[strlen(d) + 1];
        course = new char[strlen(c) + 1];
        strcpy(name, n);
        strcpy(dueDate, d);
        strcpy(course, c);
    };
    ~Task(){
        delete [] name;
        delete [] dueDate;
        delete [] course;
        delete [] saveText;
    }
};

我很确定正在发生的事情是这个函数在返回调用者后正在处理其本地声明的变量"task",该调用者调用任务的析构函数从而解除分配"任务"数组为其元素的每个成员(名称、到期、课程(引用的内存。

那么,我该如何防止这种情况发生呢?

因此,根据本网站上许多乐于助人的人的建议,我现在在我的 Task 类定义中拥有这个:

Task(const Task& t){
    name = new char[TEXT_SIZE + 1];
    dueDate = new char[TEXT_SIZE + 1];
    course = new char[TEXT_SIZE + 1];
    saveText = new char[(TEXT_SIZE * 3) + 1];
    number = t.number;
    strcpy(name, t.name);
    strcpy(dueDate, t.dueDate);
    strcpy(course, t.course);
    strcpy(saveText, t.saveText);
}

所以这应该解释三个规则之一,对吧?

您违反了三法则,因为您没有编写为Task类型提供正确深拷贝语义的复制构造函数。

您应该

使用std::string而不是char *,并让C++为您处理分配。无需调用operator new[]strcpy()operator delete[],当这些操作以std::string的形式具有更好的接口时。

如果您不能使用 std::string ,则需要为 Task 实现一个复制构造函数,该构造函数将const Task&作为其唯一参数,以及一个执行大致相同操作的赋值运算符。然后,在将Task对象复制到数组或其他位置时,代码将隐式使用此构造函数:

Task::Task(const Task& t) {
    ...
}
Task& Task::operator =(const Task& t) {
    if (this != &t) {
        ...
    }
    return *this;
}

(这两个成员往往具有非常相似的实现。分解公共代码对读者来说是一项练习。

您是否重

载了复制构造函数和赋值运算符?

您必须创建这些字符串的新副本。

您需要实施"三法则"。这意味着,只要您有一个创建资源的构造函数和一个释放资源的析构函数,您还需要创建一个复制构造函数和一个正确复制这些资源的赋值运算符。

现在,您没有复制构造函数或赋值运算符,因此不会复制这些项。

我对C++很生疏,但看起来 Task 是在堆栈上分配的,而不是在堆上分配的,所以当task超出范围时,它是未分配的。

您走在正确的轨道上,本地声明的变量task被销毁,然后释放内存。

问题是这行:

tasks[taskCount] = task;

创建 Task 对象的副本,可能通过使用 Task 类的默认复制构造函数。默认复制构造函数仅复制指针值,它不会像构造函数那样分配新内存。所以你现在必须对象,引用相同的内存,当第一个被破坏时,它会释放内存。

要解决此问题:

  1. 使用 std::string 类,而不是将字符串存储在手动分配的缓冲区中。使用 std::string 类是执行此操作的C++方法。 char*是 C 方式,不应在C++代码中使用。
  2. 处理动态分配的内存时,请使用智能指针(如 tr1::shared_ptr(,它将为您处理释放 - 在释放指向内存块的最后一个指针时解除分配。
Task task(taskCount, name, course, dueDate);
    tasks[taskCount] = task;

您在堆栈上分配一个临时变量,按值将其复制到数组中,然后C++解除分配临时变量,因为它是在堆栈上分配的。这意味着对象内的那些 char[] 指针变成了悬空指针 - 它们不再指向有效的东西。

要么使用 std::string 而不是 char[] 数组,它们按值而不是指针复制,要么修改任务数组以包含 Task* 指针(例如 std::vector<Task*> (:

    tasks[taskCount] = new Task(taskCount, name, course, dueDate);

编辑:或者正如其他人指出的那样,如果要按值复制包含指针的类,请编写复制构造函数和赋值运算符。我推荐 std::string 方法,它要简单得多。

前进:使用 std::string。请。请漂亮。我保证,自己进行内存管理会以眼泪告终。

让我们为您的 addTask 函数添加

注释
void addTask(char name[],char course[],char dueDate[])
{
    taskCount++;
    Task task(taskCount, name, course, dueDate); <--- Object 'task' created on the stack
    tasks[taskCount] = task;                     <--- Task::operator=(const Task&)
}                                                <--- Ojbect 'task' destroyed

有趣的是查看 Task::operator=(const Task&(,它执行 Task 对象的按位复制。除非明确指定,否则它如下所示:

class Task {
    char *name, *course, *dueDate;
    /* other stuff */
    /* Provided automatically by the compiler if you don't specify it */
    Task(const Task& rsh) {
        this->name = rhs.name;
        this->course = rhs.course;
        this->dueDate = rhs.dueDate;
    }
    Task& operator=(const Task& rhs) {
        this->name = rhs.name;
        this->course = rhs.course;
        this->dueDate = rhs.dueDate;
    }
};

所以你可以看到问题 - operator= 只是将指针复制到 char[] 数组,而不是整个数组。当数组被销毁时,指向它们的指针不是固定的。由于您使用的是手动内存管理,因此必须显式编写自己的 operator= 和复制结构函数,该结构函数执行"深层复制"而不是"浅拷贝"。

Task::Task(const Task& rhs) {
    this->number = rhs.number;
    this->name = new char[ strlen(rhs.name) ];
    strcopy(this->name, rhs.name);
    /* ... for other char* in the class  ... */
}
Task::operator=(const Task& rhs) {
    this->number = rhs.number;
    if ( NULL != name ) { // note, if you have a default ctor, i.e. Task::Task(), make sure it initializes char*s to NULL
        delete[] name; // otherwise you will crash when you delete unallocated memory
    }
    this->name = new char[ strlen(rhs.name) ];
    strcopy(this->name, rhs.name);
    /* ... for other char* in the class  ... */
}

这就是手动内存管理大打出手的原因!

你的复制构造函数看起来不错。您需要在堆上创建任务项,而不是在堆栈上创建任务项。 我不确定任务数组的定义位置或方式,但它需要像这样:

Task* tasks[100];
void addTask(char name[],char course[],char dueDate[]){
    Task* task = new Task(taskCount, name, course, dueDate);
    tasks[taskCount] = task;
    taskCount++;
}
 ~Task(){
    delete name;
    delete dueDate;
    delete course;
    if (saveText != NULL){
        delete saveText;
    }
}

现在,您需要一个好位置来调用清理方法,该方法将执行以下操作:

void cleanupTasks() {
    for (int i = 0; i < taskCount; i++){
        delete tasks[i];
    }
}

请注意,您没有在复制构造函数中使用 saveText 执行任何操作。 我不确定这是什么,但你需要像其他成员一样处理它。 我在析构函数中放了一个黑客来说明问题,呵呵。 我会在复制构造函数中将其分配给 NULL。