C ++ Tree类:构造函数/复制/内存泄漏?

时间:2013-08-08 19:15:02

标签: c++ memory-leaks tree

假设我有一个二叉树类,其目的是将实际间隔(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;
};

请注意,树存储指向双打a和b的指针,而不是实际的双精度数。这样做的原因是为了节省内存(和速度?),观察a和b将被许多儿童树共享(我知道双重是很“轻”但在我的实际课堂上,我有一些“更重” “)。

现在这里是主要的构造函数:

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就会被销毁,因此会删除子项等。

复制功能同样令人担忧:

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

所以问题是:编写这些函数的正确方法是什么?我也很乐意听到关于我写这门课的方式的一般评论。提前谢谢!

3 个答案:

答案 0 :(得分:1)

你在这里制造泄漏:

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

使用new分配新的Tree对象,不要在任何地方删除它。

此外,您不遵守三条规则。您创建了一个实现析构函数的类,并且三个规则表示(通常)在创建任何:copy构造函数,赋值运算符或析构函数时,通常需要所有这些。

我建议你将Tree(double * a,double * b,int depth,int maxDepth)设为静态函数,然后根据需要从公共构造函数中调用它。

答案 1 :(得分:1)

  

公共构造函数如下:

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

我知道这看起来很奇怪,我担心这可能会造成内存泄漏吗?

您(确实)正在创建内存泄漏。

一种解决方案是重写它:

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

Placement-new不会分配内存但仍会拨打电话。我不确定这是否有任何隐藏的陷阱。

但解决方案仍然很奇怪。如果您正在使用C ++ 11,则应该根据move-constructor实现(如果可能,请转发构造函数调用)。否则,你应该用std::shared_ptr(和/或pimpl成语)来实现。

编辑:另一个(更)优雅的解决方案是实施“三大半”(void Tree::swap(Tree& x);和三大);然后,你可以写:

{
     swap(Tree(&a, &b, depth, depth));
}

答案 2 :(得分:0)

我认为maxDepth对于类的每个对象都是相同的,所以将其设为static(并且只需要对其值进行一次初始化):

static int maxDepth;

然后我会在一个构造函数中重写私有和公共构造函数(你的实际公共构造函数会造成内存泄漏):

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 ... ???

你说过,你不想浪费内存,所以你使用了指针,但实际上指针也有一个大小......如果只有树的叶子包含ab (但在我看来,这不是这种情况),你可以这样做:

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则查找值。