0

実区間 (a, b) を帰納的に多くの小さな区間に分割し、中点を選択することを目的とする二分木クラスがあるとします。注意: 私が実際に書いているクラスは平面上の三角形を扱っていますが、考え方は同じです。

ヘッダー ファイル内のクラスは次のようになります。

class Tree
{
public:
    Tree(double &a, double &b, int depth);
    ~Tree();

    Tree getCopy() const;

private:        
    Tree(double *a, double *b, int depth, int maxDepth);

    double *a, *b;
    int depth, maxDepth;    
    Tree *leftChild, *rightChild;
};

ツリーは、実際の double ではなく、double の a と b へのポインターを格納することに注意してください。その理由は、メモリ (および速度?) を節約するためであり、a と b が多くの子ツリーによって共有されることに注意してください (double は非常に「軽い」ことはわかっていますが、実際のクラスでは「より重い」ものがあります。 」)。

ここにメインコンストラクターがあります:

Tree::Tree(double *a, double *b, int depth, int maxDepth) :
    depth(depth), maxDepth(maxDepth)
{    
    if (depth == maxDepth)
    {
        this->a = new double(*a);
        this->b = new double(*b);
    }
    else
    {
        this->a = a;
        this->b = b;
    }


    if (depth == 0)
    {
        leftChild = 0;
        rightChild = 0;
    }
    else
    {
        double * midpoint = new double((*a+*b)/2);
        leftChild = new Tree(a, midpoint, depth - 1, maxDepth);
        rightChild = new Tree(midpoint, b, depth - 1, maxDepth);
    }

}

そしてデストラクタ:

Tree::~Tree()
{
    if (depth == 0)
    {
        delete b;
    }
    else
    {
        delete leftChild;
        delete rightChild;
    }

    if (depth == maxDepth)
    {
        delete a;
    }
}

これらの機能が両方とも正しいことを願っています。コンストラクターがプライベートであることに注意してください。これは、再帰的に呼び出されるものです。パブリック コンストラクターは次のとおりです。

Tree::Tree(double &a, double &b, int depth)
{
    *this = *(new Tree(&a, &b, depth, depth));
}

これは奇妙に見えますが、これを行うことでメモリリークが発生するのではないかと心配していますか? しかし一方で、私が書いた場合:

    *this = Tree(&a, &b, depth, depth);

それは失敗しませんか?等価関数を考慮して、失敗する可能性があると思う理由を説明してみましょう

{
    Tree T(&a, &b, depth, depth);
    *this = T;
}

この関数が終了するとすぐに、オブジェクト T が破棄されるため、子が削除されるなどと考えています。

同じ懸念が copy 関数にも当てはまります。

Tree Tree::getCopy() const
{
    return Tree(a, b, depth, depth);
}

問題は次のとおりです。これらの関数を記述する正しい方法は何ですか? また、このクラスの書き方について一般的な意見を聞くこともできます。前もって感謝します!

4

3 に答える 3

1

ここでリークを作成しています:

Tree::Tree(double &a, double &b, int depth)
{
    *this = *(new Tree(&a, &b, depth, depth));
}

new を使用して新しい Tree オブジェクトを割り当て、どこにも削除しません。

さらに、3 のルールに従っていません。デストラクタを実装するクラスを作成しました。3 の規則によれば、(通常) コピー コンストラクタ、代入演算子、またはデストラクタのいずれかを作成すると、通常はそれらすべてが必要になります。

Tree(double *a, double *b, int depth, int maxDepth) を静的関数にし、必要に応じてパブリック コンストラクターから呼び出すことをお勧めします。

于 2013-08-08T19:38:36.040 に答える
1

パブリック コンストラクターは次のとおりです。

Tree::Tree(double &a, double &b, int depth)
{
    *this = *(new Tree(&a, &b, depth, depth));
}

これは奇妙に見えますが、これを行うことでメモリリークが発生するのではないかと心配していますか?

あなたは(実際に)メモリリークを引き起こしています。

1 つの解決策は、次のように書き直すことです。

new(this) Tree(&a, &b, depth, depth);

Placement-new はメモリを割り当てませんが、呼び出しを行います。ただし、これに隠れた落とし穴があるかどうかはわかりません。

ただし、解決策はまだ奇妙です。C++11 を使用している場合は、ムーブ コンストラクターの観点から実装する必要があります (可能であれば、コンストラクター呼び出しを転送します)。std::shared_ptrそれ以外の場合は、 (および/または pimpl イディオム) の観点から実装する必要があります。

編集:別の(より)エレガントな解決策は、「ビッグスリーとハーフ」(void Tree::swap(Tree& x);およびビッグスリー)を実装することです。次に、次のように記述できます。

{
     swap(Tree(&a, &b, depth, depth));
}
于 2013-08-08T19:40:38.460 に答える
0

maxDepthクラスのすべてのオブジェクトで同じだと思うので、それを作成しstaticます(そして、その値の初期化が1回だけ必要になります):

static int maxDepth;

そして、プライベート コンストラクターとパブリック コンストラクターを 1 つのコンストラクターに書き直します (実際のパブリック コンストラクターはメモリ リークを起こします)。

Tree::Tree(double &a, double &b, int depth)
{
    // stuff from private constructor
}

使用する型との一貫性を保つようにしてください。

double * midpoint = new double((*a + *b) / 2.0);   // 2.0 is double; 2 is int

そして最後に、私は本当に得られません:

Tree(&a, &b, depth, depth);  // why ... depth, depth ... ???

あなたは、メモリを無駄にしたくないので、ポインターを使用したと言いましたが、実際にはポインターにもサイズがあります...ツリーの葉だけがaand を含む場合b(しかし、私にはそうではないようです)ここで)、次のようなことができます:

class Tree
{
    // functors declarations

    bool isLeaf = false;
    int depth, maxDepth;    
    Tree *leftChild, *rightChild;
};

Tree::Tree(double a, double b, int depth): depth(depth) {
    if (depth == maxDepth) {
            isLeaf = true;
            this->leftChild = new double(a);
            this->rightChild = new double(b);
    }
}

そして、場合isLeaffalse子を探し、場合はtrue値を探します。

于 2013-08-08T19:34:14.397 に答える