1

大学の次のコースで C++ を使用する必要がありそうなので、初めて C++ を試してみます。私は数年間のプログラミング経験がありますが、ガベージ コレクションのない世界ではあまり経験がありません。

双方向リンク リストで使用するノードというクラスがあります。したがって、基本的には、値と他のノードへの 2 つのポインターがあります。メイン コンストラクタは次のようになりNode(const std::string & val, Node * prev, Node * next)ます。この演習には、別のノードの浅いコピーを行うコピー コンストラクターが含まれており、その上に、それを変更して深いコピーを作成するように指示するコメントが付いています。

これが私が意味すると思ったものは次のとおりです。

Node(const Node & other)
      : value(other.value)
{
  prev = new Node(other.prev->value, other.prev->prev, other.prev->next);
  next = new Node(other.next->value, other.next->prev, other.next->next);
}

これにより、コピーしたノードを変更しても新しいノードに影響を与えないようにするという目標が達成されたようです。ただし、このようにすると、ヒープに新しいものが割り当てられます。これは、ノードのデストラクタでも削除する必要があることを意味すると思うので、心配です。しかし、これは、ノードへのポインターが渡されるだけで、既に何かを指している他のコンストラクターと矛盾しています。それが起こっていると、私は正しくデストラクタに行くことができませんdeleteよね?nextprev

私は本当に混乱しています、ガイダンスに感謝します!

編集:要求されたコードは次のとおりです(上記の変更前)。

#include <string>

//! Node implements a doubly-linked list node
class Node {
    friend class LinkedList;  //!< LinkedList can access private members of Node
public:

    //!  Constructor
    Node(const std::string & v, Node * p, Node * n) :
      value(v), prev(p), next(n)
    {
    }

    //! Change to deep copy
    Node(const Node & other) :
      value(other.value), prev(other.prev), next(other.next)
    {
    }

    //!  Read-only public methods for use by clients of the LinkedList class
    const std::string & GetValue() const
    {
      return value;
    }


    Node * GetPrevious()const
    {
      return prev;
    }


    Node * GetNext()const
    {
      return next;
    }

    //! Change to deep copy
    Node & operator=(const Node & other)
    {
        if(this!=&other)
        {
            value=other.value;
            prev=other.prev;
            next=other.next;
        }
        return *this;
    }

 private:
    std::string value;        //!< value stored in the node
    Node * prev;            //!< pointer to previous node in the list
    Node * next;            //!< pointer to next node in the list
};
4

5 に答える 5

2

通常、「ディープコピー」には、データ構造をトラバースして全体をコピーすることが含まれます。あなたの場合、ノードが与えられたら、リストの完全なコピーを作成します。

于 2009-01-22T05:51:31.670 に答える
2

ディープコピーは、構造のコピー全体を作成します。構造とは、タスクを実行するために連携して機能するオブジェクトのコレクションです。各ホイールとボディのオブジェクトを持つ車のクラスがある場合、深いコピーは車全体のコピーを作成します(そしてホイールとボディの両方のコピーを作成します)。

あなたの場合、「全体の構造」がリストです。ディープコピー操作は、「リストレベル」で実行された場合にのみ意味があります。ノードのディープコピーは、ノードが指すデータをコピーしますが、それ自体をリストの一部として割り当てることはありません(ノードは「マスター」リストオブジェクトを認識しない必要があるため)。

List* List::CopyList()
{
    List* nlist = new List();
    ListNode* node = NULL, prev = NULL;
    ListNode* newNodes = new ListNode[m_nodeCount];
    int i = 0;
    while ((node == NULL && node = m_first) || (node = node->Next()) != NULL)
    {
        newNodes[i] = node->CopyNode(); // also makes a new copy of the node's data
        newNodes[i]->SetNext(NULL);
        newNodes[i]->SetPrev(prev);
        if (prev) prev->SetNext(newNodes[i]);
        prev = newNodes[i];
        ++i;
    }

    if (m_len > 0)
        nlist->SetFirst(newNodes[i]);
    if (m_len > 1)
        nlist->SetLast(newNodes[m_len - 1]);
    return nlist;
}

注:テストされていないので、そのコードをお尻から引き出しました。それが役立つことを願っています:)

于 2009-01-22T06:02:52.193 に答える
2

まず第一に、演習の目的をどのように理解すればよいかよくわかりません。コピーの深さは?あなたのようなソリューションthis->next->nextother.next->nextは、それでも同じことです。このオブジェクトも複製する必要がありますか? リストの残りは?どこで終わりますか?もちろん、リスト全体をディープ コピーすることもできますが、これは単一ノードのコピー コンストラクターのまったく予想外の動作になると思います。

おそらくvalueメンバー変数は、ディープコピーされるはずのポインターですか?それは私にとってはるかに理にかなっています。

しかし、あなたの解釈に戻ります:

Node a(...);
// ... more code that adds a whole list to a
Node b(a);

実装には 2 つの問題があります。1つはb->next->prevを指していますがa、私はそれが を指しているはずだと思いbます。次に、コーナー ケースについて考える必要がありますa。たとえば、リストの最初または最後のノードがどこになるかです。

deleteそして、あなたの主な質問に: あなたはもちろん正しいです。どこかで新しく作成されたオブジェクトを再度作成する必要があります。prevノードとリスト全体をコピーするだけnextでも、そのコピーのユーザーは、コピーされたすべてのノードを再度削除する責任があります。通常のコピーされていないリストでは、そのリストのユーザーはすべてのノードを調べて、リストを使い終わったら手動で次々と削除すると思います。彼は、1 つのノードのデストラクタがリスト全体を削除することを想定していません。同じことがコピーにも当てはまり、同じように動作する必要があります。コピーされたもののユーザーは、すべてのコピーを削除する必要があります。list(実際には、おそらくノード管理をすべて行うクラスがあるでしょう)。

しかし、繰り返しになりますが、ノードのコピー コンストラクターがリスト全体をコピーしたり、そのノードの一部だけをコピーしたりすると、これは非常に予期せぬことであり、人々は常にこれらすべてのコピーをクリーンアップすることを忘れてしまいます。しかし、それはあなたのノード クラスのせいではなく、エクササイズの要件にあります。

于 2009-01-22T06:28:14.687 に答える
2

あなたが心配するのは正しいです。

ポインターを Node コンストラクターに渡すと、ポインターで渡される所有権に関する情報はありません。これは、コンストラクターの設計が不適切です。次のノードを所有していないことを示す参照を渡すか、所有権を取得する必要があることを示す std::auto_ptr<> を渡す必要があります。next または prev が NULL (リストの最初または最後) になる可能性があるため、参照を使用できないと主張する人もいるかもしれませんが、これは代替コンストラクターを使用することで克服できます。

もちろん例外もあります:
Node クラスは別のクラスのプライベート メンバーです。この場合、Node クラスの使用は所有者によって完全に制御されるため、その正しい使用は所有するクラスによって制御されます。

あなたが提供していないのは、デストラクタの定義です。これにより、ノードが実際にコンストラクターに渡されたポインターの所有権を取得しているかどうか (または、次と前のポインターが既にスマート ポインターであるかどうか) を知ることができます。

于 2009-01-22T06:28:20.473 に答える
1

すべてのノードがそれが指すノードのコピーを作成する場合、デストラクタ内のオブジェクトを安全に削除できます。ポインターを渡す場合 (コンストラクター Node(const std::string & v, Node * p, Node * n) のように) は、ポインターを「所有」しておらず、削除しないでください。これがリンク リスト クラスの一部である場合、そのクラスはポインタを所有し、必要に応じてオブジェクトを削除する必要があります。Node をリンク リスト クラスのプライベート サブクラスにして、ユーザー (または自分自身) がポインターをいじるのを避けることもできます。

実装でも再帰を間違えました。コピー コンストラクターには 1 レベルのディープ コピーが含まれており、ポインターを受け取る「通常の」コンストラクターを呼び出して浅くしています。これが意味することは、ディープ コピーの深さが 1 レベルのみであることです。次のように、コピー コンストラクターを繰り返し呼び出す必要があります。

Node(const Node & other) : value(other.value)
{
  prev = new Node(*(other.prev));
  next = new Node(*(other.next));
}

私の知る限り、ここでディープコピーを使用する利点はありません。私が考えることができる唯一の実用的なアプリケーションは、リスト全体をコピーするときです。これは、上記のリストを表すクラスでより効果的に処理できます。

于 2009-02-02T12:12:15.610 に答える