2009-10-09 3 views
0
#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 << , но мне интересно, что случилось с _Z после установщика(), у меня пока нет больших знаний в указателях/ссылках, но для логики я предполагаю, что он заменяется на новую переменную , если она протекает, есть ли все равно, чтобы добиться этого, не имея снова установить на номер _Z? , потому что адрес не изменился, только выделенная прямая память Я собирался использовать *& вместо простых указателей, но может ли это иметь значение?указатели и ссылки вопрос

+0

Вы всегда можете использовать valgrind для проверки утечек памяти , Это очень эффективно. –

+0

Да, я использовал это иногда раньше с C, и это было довольно хорошо. – Jonathan

+5

Я думаю, что лучший способ быть уверенным в том, чтобы не утечка памяти - это не пытаться снять сумасшедшие трюки. Что это: класс, цель которого состоит в том, чтобы удерживать целое число ** и ** пытаться управлять памятью другого динамически распределенного экземпляра самого себя? – UncleBens

ответ

5

Существует утечка памяти на этой линии:

b->Z(new Teste); 

из-за определения функция:

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

Похоже, что Z без аргументов должен был быть геттером и с аргументами сеттера. Я подозреваю, что вы имели в виду сделать:

void Z(Teste *value){ 
    value->AnyNum = 100; 
    _Z = 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; 
} 
+0

спасибо, что уточнил мой вопрос. для этой цели shared_ptr не нужно удалять вещи в деструкторе? – Jonathan

+0

Назначение shared_ptr <> и auto_ptr <> и других интеллектуальных указателей состоит в том, чтобы не нужно явно удалять данные, но удалять их автоматически. Умные указатели не идеальны, но они устраняют целые классы жестких проблем. –

+0

shared_ptr защищает ресурсы с использованием шаблона RAII (http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization). Хороший побочный эффект - вам не нужно писать какие-либо явные удаления, и он разъясняет семантику владения, т. Е.ли Teste «принадлежит» (отвечает за удаление) указателей, которые он передал? Неясно в оригинале, но в переписанном коде ясно, что Teste не «владеет», а «разделяет» их. –

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); 
} 
+0

Конструктор копирования должен быть создан вручную? например: deepcopy (a, b) {a-> m = b-> m; } и т. Д. – Jonathan

+0

Если ваш класс имеет указатель на что-либо вне себя, и это не просто что-то для его использования (например, указатель файла для класса записи файлов), ему необходимо убедиться, что он скопирован правильно и удалены должным образом. Обычно ему нужен ручной деструктор, конструктор копирования и оператор присваивания. –

+0

Функция Z не проверяет нулевой указатель _z. У вас есть конструктор, который может оставить _z null, поэтому это (с другим основным) может привести к сбою. «b-> Z (новый тест); вероятно, должно быть «b-> Z (Teste()); (используйте временный, убедитесь, что он уничтожает). Хотя AraK правильно относится к проверке NULL деструктора, вы можете сохранить его в любом случае - он предупреждает читателей, что указатель может быть нулевым. О, и вы должны изучить умные указатели * и * тупые указатели, поэтому IMO вы получаете кредиты для изучения этого материала, несмотря на комментарии умных указателей. – Steve314

2

У вас есть еще одна проблема: _z не является идентификатор, который следует использовать. В общем, лучше избегать подчеркивания подчеркивания, и в частности двойные подчеркивания или подчеркивания, за которыми следует большая буква, зарезервированы для реализации.

+0

ср. http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-ac-identifier/228797#228797 –

+0

Да, это был только пример – Jonathan

+0

Я не понять эти правила ... Например, если у меня есть член, называемый _Hmm, он не будет затронут ОС или что-то в этом роде, верно? директивы - __STUFF__ и т. д. Если они не влияют на него, используя подчеркивание или нет, то до таких разработчиков? Просто вопрос! xD – Jonathan

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); 
+2

Для чего вам нужен класс? Что не так с простой функцией? 'template void do_delete (T * & p) {delete p; p = 0;}' – sbi

+0

В чем проблема с простым макросом? – Jonathan

+1

@sbi функциональность действительно то же самое, я использовал структуру, потому что ее можно передать в std :: for_each и т.п. @ Jonathan вам следует избегать макросов для всего, что может быть выражено и с использованием языка. Вот прекрасный пример того, что проблема с простым макросом: http://www.parashift.com/c++-faq-lite/inline-functions.html#faq-9.5 – stijn

0

void Z (значение Teste *) { value-> AnyNum = 100; * _Z = * значение; }

и

b->Z(new Teste); 

создает утечку памяти

'новый Teste' никогда не будет удален, а не то, что вы делаете, выделение нового объекта в качестве параметра, затем копирует все, что там, используя значение * _Z = *, но объект не удаляется после вызова.

, если вы напишете

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

было бы лучше

конечно большинство будет использовать повышение :: shared_ptr или что-то подобное, чтобы избежать заботы о удалять и такие

+0

новый Teste получает удаление на деструкторе класса, но старший _Z никогда не удаляется, не так ли? boost? быстрый google сказал мне, что это очень известная библиотека. Возможно, я посмотрю позже, да – Jonathan

+0

да, это правильно. boost также поможет вам. –

2

Какой беспорядок! Вся программа очень трудно читать из-за выбора имен идентификаторов, чтобы начать с:

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

Я считаю, что очень некрасиво. При использовании классов это кажется очень неудобным. Вы можете использовать его там, где переменные выходят за пределы области видимости, но это трата времени в деструкторе. Я думаю, что было бы Asier обернуть код в некоторых смарт-указатель:


class Teste 
{ 
    private: 
     Teste *_Z; 

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

Ok. У вас есть указатель, который вы удаляете в деструкторе. Это означает, что вы берете на себя ответственность за указатель. Это означает, что применяется ule из четырех (аналогично правилу трех, но применимо к правилам собственности). Это означает, что вам в основном нужно написать 4 метода или сгенерированные версии компилятора испортит ваш код. Методы, которые вы должны написать:

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

В вашем коде только два из них. Вам нужно написать два других. Или ваш объект не должен владеть указателем RAW. то есть. используйте Smart Pointer.


Teste *_Z; 

Это не допускается. Идентификаторы, начинающиеся с символа подчеркивания и буквы capitol, зарезервированы. Вы рискуете превратить макрос ОС в свой код. Прекратите использование подчеркивания в качестве первого символа идентификаторов.


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

Это не требуется. Простое удаление _Z было бы в порядке. _Z выходит из области видимости, потому что он находится в деструкторе, поэтому нет необходимости устанавливать его в NULL. Оператор delete обрабатывает NULL-указатели просто отлично.

~Test() 
{ delete _Z; 
} 

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

Что произойдет, если вы звоните Z() несколько раз (PS поставив * рядом с Z, а не рядом с его сделать Тест трудно читать). Каждый раз, когда вы вызываете 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(); 

Получение объекта аб для. Но кто уничтожал объект a или b?

b->Z(new Teste); 

После этого он становится слишком запутанным.

+0

спасибо за сообщение, это было очень полезно. Этот код, который я написал, не является моим проектом, я просто пытался понять некоторые концепции над ссылками и ptr. Вы сказали, что когда вы «владеете» переменной, тогда получатель должен возвращать ссылку вместо указателя вправо? Я проверю об этом! Это решает мой вопрос. Я не был хорошо разбираюсь в моем вопросе ... что я хотел знать, так это, чтобы убедиться, что * указатель все еще можно использовать после использования метода setter, понимаете? Мой английский терпит неудачу, иногда слова просто не приходят ... Большое спасибо за помощь, вы и все. Этот сайт просто классный. – Jonathan

+0

Привет, оператор присваивания подобен конструктору копирования? То есть: следует назначать путем удаления и копирования с A на B? – Jonathan

+0

В принципе да. Но вы можете просто скопировать значение указателя. Вы должны скопировать содержимое. –

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; 

Это не большой сделка, учитывая, что конкретное условие, но оно будет взорваться чем-нибудь еще, и вы потратите в возрасте, пытаясь понять, почему ваши указатели являются sudde NULL. Вот почему встроенные функции предпочтительны для макросов - их сложнее испортить.


Редактирование: хорошо, вы использовали запятую в определении макроса, чтобы вы были в безопасности ... но я бы все же сказал, что безопаснее использовать функцию [inline] в этом случае. Я не являюсь одним из тех, кто не занимается массовым использованием, но я бы не использовал их, если они не являются абсолютно необходимыми, и их нет в этом случае.

Смежные вопросы