【问题标题】:C++ dynamic memoryC++ 动态内存
【发布时间】:2011-10-12 03:56:52
【问题描述】:

这个问题:在这个函数的最后,“tasks[taskCount]”元素的成员名字,截止日期等,确实是传递给这个函数的,但是在返回给这个函数的调用者之后,所有这些值都变成垃圾,除了不是动态的taskcount。这个函数是在类“Tasklist”的范围内定义的

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;
    }
};

我很确定发生了什么是这个函数在返回到调用者之后处理其本地声明的变量“任务”,调用者调用了任务的析构函数 从而释放“tasks”数组为它的每个元素的成员(名称、到期、课程)引用的内存。

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

因此,根据本网站上许多乐于助人的人的建议,我现在在我的任务类定义中添加了这个:

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。如果您仍在摆弄三规则,那么您就错过了:Task& operator=(Task t) { swap(number, t.number); swap(name, t.name); swap(dueDate, t.dueDate); swap(course, t.course); swap(saveText, t.saveText); return *this;}

标签: c++ memory pointers dynamic


【解决方案1】:

您违反了the rule of three,因为您没有编写为您的Task 类型提供适当深复制语义的复制构造函数。

【讨论】:

  • 但是他也应该把资源管理和业务逻辑分开aka separation of concerns;为拥有多个拥有指针的对象实施规则 3 是个坏主意。
【解决方案2】:

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

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

Task::Task(const Task& t) {
    ...
}

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

(这两个成员的实现往往非常相似。分解出公共代码是读者的练习。)

【讨论】:

  • 三法则说除了复制构造函数之外,你还必须实现赋值运算符。诚然,仅复制构造函数就可以解决 OP 的直接问题,但这一切都是为了长远考虑。 ;-)
  • 宾果游戏。它是复制构造函数。谢谢谢谢谢谢!!
  • @Colten:不要这样做。为您的成员使用std::string,并让编译器生成复制构造函数。如果有人问你的类是做什么的,你应该总是能用一件事情来回答,但现在你不能:它处理字符串的管理 做一些其他的事情。不好。
  • @GMan 我的帖子也提到了——std::string 通常是首选,但同时一个复杂的类需要实现三大类——简单地说“使用std::string”就赢了'不解释为什么有问题,它只是将它推迟到STL。
  • @Jonathan Grynspan:这样做也隐藏了您拥有三个动态全涂层对象的复杂性。而且您还没有考虑到任何一个失败将如何影响其他两个。为包含多个指针的类定义复制构造函数和赋值运算符是一种糟糕的设计。如果你想向他展示它是如何完成的,你应该将分配/取消分配分解为另一个类,而不是处理一个指针,然后在任务中使用这个对象。这样做也会更加明显,这已经由 std::string 完成。
【解决方案3】:

你是否正确地重载了​​复制构造函数和赋值运算符?

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

【讨论】:

  • 我认为答案应该看起来像一个答案,即没有问号,我想你是正确的并且你知道,所以应该更有信心:)
  • @unkulunkulu:将反问作为答案并没有错。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

      问题在于这一行:

      tasks[taskCount] = task;
      

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

      解决这个问题:

      1. 使用std::string 类而不是将字符串存储在手动分配的缓冲区中。使用std::string 类是执行此操作的 C++ 方式。 char* 是 C 方式,不应该在 C++ 代码中使用。
      2. 在处理动态分配的内存时,请使用智能指针(例如 tr1::shared_ptr),它会为您处理解除分配问题 - 当指向内存块的最后一个指针被释放时解除分配。

      【讨论】:

      • 有道理!不幸的是,这是我们不能使用 std::string 类的类中的赋值。我将在浅拷贝和深拷贝上查看我的课程内容,看看这是否能让我有所收获。
      • 在这种情况下,您必须查看 三规则,就像其他人建议的那样。
      【解决方案7】:
      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 方法,它更简单。

      【讨论】:

        【解决方案8】:

        转发:使用 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  ... */
        }
        

        这就是手动内存管理失败的原因!

        【讨论】:

        • 是的,不幸的是,这是作业的要求。但是,你是绝对正确的,我已经实现了一个复制构造函数,它现在运行顺利。谢谢!
        【解决方案9】:

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

        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 做任何事情。我不确定这是什么,但你需要像其他成员一样处理它。我在析构函数中加入了一个hack来说明问题,呵呵。我会在复制构造函数中将它分配给 NULL。

        【讨论】:

          猜你喜欢
          • 2013-09-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-07
          • 2015-06-27
          • 2021-02-28
          • 2015-12-13
          相关资源
          最近更新 更多