【问题标题】:How to return a List in C++如何在 C++ 中返回一个列表
【发布时间】:2014-05-24 11:59:53
【问题描述】:

假设我有一个 List 类:

class List
{
    private:
        class Node{
            public:
                int data;
                Node* next;
            public:
                virtual ~Node()
                {
                    if (next != NULL)
                        delete next;
                }
        };

        Node* head;
    public:
        virtual ~List()
        {
            if (head != NULL)
            {
                delete head;
            }
        }
    public:
        void AddNode(int data);
        void DeleteNode(int data);
        //....  
};

现在我想实现一个函数,它将两个 List 引用作为参数并返回一个新创建的 List:

List SumTwoList(List& list_1, List& list_2)
{
    //here I create a new List list_3 and add some elements to it based on list_1 and list_2(add operation use dynamic allocation).

    //finally I want to return this list_3.
    return list_3;
}

我在这里感到困惑。我应该如何在SumTwoList() 中创建这个list_3。如果我只是将其设为局部变量,则当函数返回时,list_3 的析构函数将释放所有添加到列表中的节点并销毁整个列表。但是,如果我做动态分配来创建这个list_3 并返回一个指向它的指针,使用后删除这个列表是用户的责任。

我不知道如何从这两个想法中进行选择,我很确定有一些更好的方法可以解决这个问题。提前感谢您的建议。 :-)

【问题讨论】:

  • 您要退回一份副本。销毁的不是返回的副本。当然,这需要一个适当的复制构造函数,而不仅仅是浅拷贝指针。
  • @user3352668 - 您需要实现复制构造函数和赋值运算符。一旦你这样做并确认这两个函数可以工作,那么你的 sn-p 代码应该可以工作。顺便说一句,你的析构函数不起作用。
  • @chris 该副本是类 List 的副本,并非每个节点都与该列表相关联。两者的头指针会指向同一个节点。当一个被销毁时,它会删除列表中的所有节点,所以类List的新副本没有意义。
  • @user3352668,这就是为什么你应该有一个不会像克里斯提到的那样浅拷贝元素的复制构造函数
  • @adrin,如果 (N)RVO 不适用,这通常已经被 (N)RVO 和 C++11 中的移动语义覆盖。

标签: c++ list return


【解决方案1】:

添加一个复制构造函数和赋值运算符(并修复你的析构函数):

class List
{
    private:
        typedef struct node{
            int data;
            struct node* next;
        }NODE;

        NODE* head;
    public:

    virtual ~List()  // Please fix this, as others have mentioned!
    { }
    List(const List& rhs) 
    {
       // this needs to be implemented
    }
    List& operator = (const List& rhs) 
    {
       // this needs to be implemented
    }
    //...
};

“这需要实施”是您需要填写的内容才能使副本正常工作。

那么这给你带来了什么?首先,您现在可以按照您在问题中尝试执行的形式编写函数。也就是说,您可以安全地按值返回列表,而无需进行任何进一步的编码来动态分配或删除列表。

List SumTwoList(List& list_1, List& list_2)
{
   List list_3;
   // do stuff to add data to list_3...
   //...
   //finally I want to return this list_3. 
   return list_3;
}

其次,假设我们要将一个列表复制到另一个列表。这可以使用“=”或简单的复制构造轻松完成

如果您不想复制,则通过将 copy-ctor 和 assignment op 设为私有且未实现*来关闭复制,否则复制将是使用您的类的程序员的一个选项,编译器也会在复制时进行复制需要。

换句话说,如果正确实现了复制,那么像这样的简单程序应该可以工作:

int main()
{
   List lis1;
   // Add some nodes to lis1...
   //...
   // Assume that lis1 now has nodes...complete the copying test
   List lis2(lis1);
   List lis3;
   lis3 = lis1;
}

上面的程序应该没有内存泄漏和崩溃,即使 main() 返回。如果不是,则复制和/或破坏将被破坏。

*C++11 允许您在定义函数时使用 delete 关键字,以比使用 C++11 之前的代码更简单的方式禁用复制构造函数和赋值运算符。

【讨论】:

    【解决方案2】:

    一种选择是返回一个指向新List的指针

    List * SumTwoList(List& list_1, List& list_2)
    {
        List * pResultList = new List;
    
        // Iterate over all elements of list_1 and add (append) them to pResultList
        // Iterate over all elements of list_2 and add (append) them to pResultList
    
        return pResultList;
    }
    

    但是,它的缺点是调用者必须记住 delete 返回的对象。

    相反,最好稍微不同地设计 API,例如将 list_2 的元素附加到现有列表中

    // Append contents of rhs to this. rhs stays as is
    List::append( List const & rhs );
    

    调用者可以将其称为

    List list_1;
    // work on list_1, such as add elements
    List list_2;
    // work on list_2, such as add elements
    
    list_1.append( list_2 );
    

    这与std::list::splice() 很接近,但并不完全如此。 splice() 移动元素从 rhsthis,但 append() 只是复制元素。

    注意,你的析构函数有问题,是delete'ing only thehead,后面的元素泄露了。

    析构函数实现示意图

    List::~List() {
        while( head ) {
            struct node * oldHead = head;
            head = head->next;
            delete oldHead;
        }
        head = NULL;
    }
    

    【讨论】:

    • -1:However, if I do dynamic allocation to create this list_3 and return a pointer to it, it is the user's responsibility to delete this list after using。您的回答提供了尚未说明的问题?
    • @Paranaix:我知道,我已经在答案本身中提到了这个缺点。我还添加了另一种设计 API 的方法。
    • @Arun 我已经修改了 List 类。我认为现在析构函数工作得很好。我现在要删除整个列表吗?
    • @user3352668:抱歉,这也可能行不通。请查看我的更新答案,我已经包含了析构函数的草图。
    • @Arun 我看到了你的回答。这样绝对可以删除所有内容。但是,在我的方法中,一旦 List 删除第一个节点,第一个节点的第一个析构函数将删除第二个节点......所以我认为最后每个节点都会被删除。此外,您的方法适用于列表。如果我有一棵树怎么办?如果我们不使用父节点来删除其子节点,则很难实现析构函数。我真的是一个初学者,所以我不确定我是否正确:)
    【解决方案3】:

    您将返回一份列表的副本。如果将实现类 List 的复制构造函数和赋值运算符,这将达到您的预期。

    class List
    {
        private:
        //...
        public:
            List();
            List( const List& other);
            List& operator= ( const List& other);
        //...
    };
    

    也许更好的选择是创建一个构造函数,它接受两个 Lists 并通过合并它们来构造一个新的(做所有这些 SumTwoList 会做的事情):

    class List
    {
        private:
        //...
        public:
            List( const List& first, const List& second) { // similar to SumTwoList
            }
        //...
    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-19
      相关资源
      最近更新 更多