指针和参考问题

时间:2009-10-09 13:30:20

标签: c++ memory pointers set

#ifndef DELETE
    #define DELETE(var) delete var, var = NULL
#endif

using namespace std;

class Teste {
    private:
        Teste *_Z;

    public:
    Teste(){
        AnyNum = 5;
        _Z = NULL;
    }
    ~Teste(){
        if (_Z != NULL)
            DELETE(_Z);
    }

    Teste *Z(){
        _Z = new Teste;
        return _Z;
    }
    void Z(Teste *value){
        value->AnyNum = 100;
        *_Z = *value;
    }

    int AnyNum;
};

int main(int argc, char *argv[]){
    Teste *b = new Teste, *a;

    a = b->Z();

    cout << "a->AnyNum: " << a->AnyNum << "\n";

    b->Z(new Teste);

    cout << "a->AnyNum: " << a->AnyNum << "\n";

    //wdDELETE(a);
    DELETE(b);
    return 0;
}

我想知道此代码中是否存在内存泄漏 它工作正常,*a设置两次,AnyNum在每个cout <<上打印不同的数字 但是我想知道在setter(_Z)之后new Teste发生了什么,我对指针/引用还没有太多的了解,但是对于逻辑我猜它是换成了新的变量 如果它正在泄漏,无论如何都要完成这个而不必再次设置为_Z? 因为地址没有改变,只是分配了直接内存 我打算使用*&而不仅仅是指针,但它会有所不同吗?

7 个答案:

答案 0 :(得分:5)

此行存在内存泄漏:

b->Z(new Teste);

因为函数的定义:

void Z(Teste *value){
    value->AnyNum = 100;
    *_Z = *value;
}

看起来Z没有参数应该是一个getter并且带有参数的setter。我怀疑你打算这样做:

void Z(Teste *value){
    value->AnyNum = 100;
    _Z = value;
}

(注意第三行)也就是说,将指针“value”赋给指针“_Z”而不是复制指向Z指向的值的值。有了这个,第一个内存泄漏将被解决,但代码仍然有一个,因为_Z可能已经持有一个指针。所以你必须这样做:

void Z(Teste *value){
    value->AnyNum = 100;
    delete _Z; // you don't have to check for null
    _Z = value;
}

如另一条评论所述,真正的解决方案是使用智能指针。对于相同的代码,这是一种更现代的方法:

using namespace std;

class Teste {
    private:
        boost::shared_ptr<Teste> Z_;

    public:
    Teste() : AnyNum(5), Z_(NULL)
    { }

    boost::shared_ptr<Teste> Z()
    {
        Z_.reset(new Teste);
        return Z_;
    }

    void Z(boost::shared_ptr<Teste> value)
    {
        value->AnyNum = 100;
        Z_ = value;
    }

    int AnyNum;
};

int main(int argc, char *argv[]){
    boost::shared_ptr<Teste> b = new Teste, a;

    a = b->Z();

    cout << "a->AnyNum: " << a->AnyNum << "\n";

    b->Z(boost::shared_ptr<Teste>(new Teste));

    cout << "a->AnyNum: " << a->AnyNum << "\n";

    return 0;
}

答案 1 :(得分:4)

是的,有:

void Z(Teste *value)
{
   value->AnyNum = 100;
   *_Z = *value; // you need assignment operator
}

编译器生成的赋值运算符不会生成深层副本,而是生成浅层副本。你要做的是为Teste编写一个合适的赋值运算符(可能还有一个复制构造函数)。此外,在删除指针之前,您不必检查指针是否为NULL:

~Teste()
{
   // no need for checking. Nothing will happen if you delete a NULL pointer
   if (_Z != NULL)
     DELETE(_Z);
}

答案 2 :(得分:2)

您还有另一个问题:_Z不是您应该使用的标识符。一般情况下,最好避免使用下划线,特别是保留大写字母后跟大写字母的双下划线或下划线。

答案 3 :(得分:2)

真是一团糟! 由于选择了标识符名称,整个程序很难阅读:

#ifndef DELETE
    #define DELETE(var) delete var, var = NULL
#endif

我觉得很难看。 在使用课程时,它看起来非常不合适。你可以在变量超出范围的地方使用它,但它在析构函数中是浪费时间。我认为将代码包装在一些智能指针中更为简单:


class Teste
{
    private:
        Teste *_Z;

    public:
        Teste()
        ~Teste()    // Delete the _Z pointer.
        Teste *Z();
        void Z(Teste *value);
};

确定。您有一个在析构函数中删除的指针成员。 这意味着您将获得指针的所有权。这意味着四分之一适用(类似于三条规则但适用于所有权规则)。这意味着你基本上需要编写4个方法,否则编译器生成的版本会搞乱你的代码。你应该写的方法是:

A Normal (or default constructor)
A Copy constructor
An Assignment operator
A destructor.

您的代码只有两个。你需要写另外两个。 或者您的对象不应该拥有RAW指针的所有权。即。使用智能指针。


Teste *_Z;

这是不允许的。 以下划线和国会大厦字母开头的标识符是保留的。 您冒着操作系统宏损坏代码的风险。停止使用下划线作为标识符的第一个字符。


~Teste(){
    if (_Z != NULL)
            DELETE(_Z);
}

这不是必需的。简单删除_Z本来没问题。 _Z超出范围,因为它在析构函数中,因此不需要将其设置为NULL。 删除操作符正好处理NULL指针。

~Test()
{    delete _Z;
}

Teste *Z(){
    _Z = new Teste;
    return _Z;
}

如果多次调用Z()会发生什么情况(PS将*放在Z旁边而不是旁边的Teste使其难以阅读)。 每次调用Z()时,成员变量_Z都会被赋予一个新值。但旧的价值会发生什么?基本上你是在泄漏它。还通过返回指向所拥有对象的指针 在Teste内部,你给别人机会滥用对象(删除它等)。这个不好。这种方法没有明确的所有权。

Teste& Z()
{
    delete _Z;       // Destroy the old value
    _Z = new Teste;  // Allocate a new value.
    return *_Z;      // Return a reference. This indicates you are retaining ownership.
                     // Thus any user is not allowed to delete it.
                     // Also you should note in the docs that it is only valid
                     // until the next not const call on the object
}

void Z(Teste *value){
    value->AnyNum = 100;
    *_Z = *value;
}

您正在将新构造的对象(包含指针)的内容复制到另一个动态创建的对象中! 如果没有先分配_Z会发生什么。构造函数将其设置为NULL,因此无法保证它具有有效值。 您分配的任何对象也应删除。但是这里的值被动态分配传递给Z但从未被释放。你逃避这个的原因是因为指针是c 当析构函数被销毁时,选择_Z和_Z被删除。


Teste *b = new Teste, *a;

真的听说过。不要懒得把它写出来。 这被认为是糟糕的风格,你永远不会通过任何代码审查。

Teste* b = new Teste;
Teste* a; // Why not set it to NULL

a = b->Z();

获取ab对象。但谁破坏了对象a或b?

b->Z(new Teste);

之后就太复杂了。

答案 4 :(得分:1)

(我试图将其添加为评论,但这会搞砸代码..)

我强烈建议不要使用

#ifndef DELETE
  #define DELETE(var) delete var, var = NULL
#endif

但更像是

struct Deleter
{
  template< class tType >
  void operator() ( tType*& p )
  {
    delete p;
    p = 0;
  }
};

用法:

Deleter()( somePointerToDeleteAndSetToZero );

答案 5 :(得分:1)

(不是真正的答案,但评论不会这样做)

你定义你的宏的方式很容易出现一个微妙的错误(而且到目前为止没有人发现它的事实证明了这一点)。考虑一下你的代码:

if (_Z != NULL) // yes, this check is not needed, but that's not the point I'm trying to make
                DELETE(_Z);

预处理器通过后会发生什么:

if (_Z != 0)
        delete _Z; _Z = 0;

如果您仍然无法看到它,请让我正确缩进:

if (_Z != 0)
        delete _Z;
_Z = 0;

这不是什么大问题,特别是条件,但它会爆炸其他任何东西,你会花年龄试图弄清楚为什么你的指针突然是NULL。这就是为什么内联函数比宏更受欢迎的原因 - 将它们弄得更加困难。

<小时/> 编辑:好的,你在宏定义中使用了逗号,这样你就安全了......但我仍然会说在这种情况下使用[inline]函数更安全。我不是那些不使用宏的人之一,但我不会使用它们,除非它们是绝对必要的并且它们不是这种情况

答案 6 :(得分:0)

  

void Z(Teste * value){           value-&gt; AnyNum = 100;           * _Z = *值;       }

b->Z(new Teste);

创建内存泄漏

'新Teste'永远不会被删除,而是你正在做的是分配一个新对象作为参数,然后使用* _Z = *值复制其中的任何内容,但是在调用后对象不会被删除。

如果你要写

Test* param - new Teste;
b->Z(param)
delete param;

会更好

当然大多数人会使用boost :: shared_ptr或类似的东西来避免关心删除等等