О д ++ деструкторах - PullRequest
       3

О д ++ деструкторах

7 голосов
/ 07 августа 2011

У меня есть некоторый опыт Java, и я новичок в C ++.

ниже мой код, его вывод:

0 1 2 3 4 5 6 7 8 9
destructor ---s1
8791616 8785704 2
destructor ---s1

Я ожидал следующий вывод:

0 1 2 3 4 5 6 7 8 9
destructor ---abc
0 1 2
destructor ---s1

Я не могу понять, почему деструктор высвобождает ресурс первого объекта.Как я могу распечатать вывод, который я ожидал?

#include <iostream>
using namespace std;
class Sequence{
    public:
        Sequence(int count=10,string name = "abc");
        void show();
        ~Sequence();

        int* _content;
        int _count;
        string _name;

};

Sequence::Sequence(int count,string name){
    _count = count;
    _content=new int[count];
    _name = name;
    for(int i=0;i<count;i++){
        _content[i]=i;
    }
}

Sequence::~Sequence(){
    cout << "destructor ---"<<_name<<endl;
    delete [] _content;
}

void Sequence::show(){
    for(int i=0;i<_count;i++)
        cout<<_content[i]<<" ";
    cout<<endl;
}

int main(){
    Sequence s1 = Sequence();
    s1.show();
    s1 = Sequence(3,"s1");
    s1.show();
}

Ответы [ 6 ]

6 голосов
/ 07 августа 2011

Если вы повысите уровень предупреждения на вашем компиляторе, вы получите подсказку, что ваш класс содержит указатели, но вы не определяете Sequence(const Sequence&) или operator=(const Sequence&) (см. Что такое правило трех? ).

Поскольку вы не предоставляете конструктор копирования или оператор присваивания, их предоставляет компилятор, который выполняет присваивание по элементам.

Когда вы вызываете s1 = Sequence(3,"s1");,вы делаете следующее (это может быть неожиданным для разработчика Java):

  • Создание нового временного Sequence из трех с именем s1
  • Назначениеэто s1, что:
    • устанавливает si._content в качестве указателя на новый массив из трех только что созданных ints, утечка старого из 10.
    • sets *От 1023 * до 3
    • устанавливает si._name до "s1"
  • временно не s1) затем уничтожается (в вашем фактическом выводе выше вы видите, что «s1» уничтожается дважды), оставляя _content, указывая на свободную память (вот почему уво втором вызове вы увидите мусор s1.show()).

Если вы объявите такой оператор присваивания, как этот, вы получите нечто ближе к ожидаемому результату:

Sequence& operator =(const Sequence& rhs)
{
    if (this != &rhs)
    {
        delete [] _content;

        _count = rhs._count;
        _content = new int[_count];
        _name = rhs._name + " (copy)";
        for (int i = 0; i < _count ; ++i)
        {
            _content[i] = rhs._content[i];
        }
    }
    return *this;
}

Однако вы не увидите:

destructor ---abc

... потому что вы не уничтожаете s1, в то время как _name содержит "abc".

s1 уничтожается, когда выходит из области видимости при закрытии }, поэтому вы видите второй вызов деструктора.С вашим кодом это вызывает delete[] на s1._content во второй раз (он был удален во временном, как вы помните).Это может привести к сбою в самом конце вашей программы.

Я добавил " (copy)" к _name в своем операторе присваивания, чтобы проиллюстрировать, что здесь происходит.

Пожалуйста, взгляните также на Что такое иероглиф копирования и обмена? , который является очень аккуратным способом работы с классами с необработанными указателями.Это также сгенерирует желаемый результат, например, экземпляр s1 с _name из "abc" выводит и уничтожает swap.Я реализовал это здесь , а также несколько других небольших улучшений, чтобы вы могли видеть его работу.

NB : канонический способ создания экземпляракласс:

Sequence s1; // Default constructor. Do not use parentheses [http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.2]!
Sequence s2(3, "s2") // Constructor with parameters
3 голосов
/ 07 августа 2011

Объекты C ++ довольно сильно отличаются от объектов Java, и вы сталкиваетесь с общей путаницей среди новичков в C ++. Вот что происходит:

Sequence s1 = Sequence();

Это создает новую последовательность, s1, с конструктором по умолчанию (РЕДАКТИРОВАТЬ: по крайней мере, это то, что происходит в распечатке выше, хотя, как указали несколько комментаторов, вполне допустимо для этого создать временную последовательность, которая затем назначается вместо s1 через конструктор копирования).

s1.show();

Это печатает данные на s1.

s1 = Sequence(3,"s1");

Здесь все немного запутано. В этом случае происходит следующее:

  1. Новый анонимный объект Sequence создается с параметрами 3, "s1"
  2. Этот анонимный объект копируется (по значению) в s1, используя operator = (оператор копирования)
  3. Анонимный объект Sequence выпадает из области видимости и удаляется

Далее, последний

s1.show();

снова вызывает show () для исходного объекта s1, но его данные теперь являются копией анонимных данных.

Наконец, s1 выходит из области видимости и удаляется.

Если вам нужны объекты, которые ведут себя больше как объекты Java, вам нужно обрабатывать их как указатели, например,

Sequence *s1 = new Sequence();  // constructor
s1->show();  // calling a method on a pointer
delete s1;  // delete the old one, as it is about to be assigned over
s1 = new Sequence(3,"s1");  // assign the pointer to a new Sequence object
s1->show();
delete s1;

Если вы хотите немного упростить управление памятью, загляните в boost :: shared_ptr, который обеспечивает подсчет ссылок (а не сборщик мусора) для автоматического управления памятью.

2 голосов
/ 07 августа 2011

Так просто, как я могу:

Sequence s1 = Sequence(): созданная по умолчанию последовательность (не конструктор копирования), временный, деструктор не вызывается.

s1.show(): печать значений в s1._content.

s1 = Sequence(3,"s1");: Создает временный объект, использует неявный конструктор копирования для присвоения значений s1. Удаляет временный, вызывая деструктор и, следовательно, делает недействительным указатель (_content) в s1 и временный.

s1.show(): неопределенное поведение при печати из недопустимого указателя.

Затем, когда s1 выходит из области видимости, он пытается удалить s1._content; более неопределенное поведение.

1 голос
/ 07 августа 2011

Позвольте мне объяснить, что происходит в вашей основной функции:

Sequence s1 = Sequence();

После выполнения этой строки произошло несколько вещей:

  1. s1 создается с ctor по умолчанию.
  2. Sequence() справа также создает неназванный объект временной последовательности с ctor по умолчанию.
  3. временный объект копируется в s1 с помощью функции operator = по умолчанию, предоставляемой компилятором. Таким образом, каждое поле члена s1 содержит одинаковые значения временного объекта. Обратите внимание, что указатель _content также копируется, поэтому s1._content указывает на данные, динамически выделяемые для указателя _content объекта temp.
  4. Тогда временный объект разрушается, потому что он выходит за пределы своей области видимости. И это вызывает освобождение памяти на _content указатель временного объекта. Однако, поскольку, как упомянуто в 3, s1._content указывает на этот блок памяти, это освобождение приводит к тому, что s1._content теперь указывает на уже освобожденный блок памяти, что означает, что вы получили данные мусора в этом блоке памяти.

Так что к этому времени ваше окно вывода должно иметь: деструктор --- abc

s1.show(); this shows the garbage data to the output window:

-572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -57 2662307 -572662307 -572662307

Аналогично, s1 = Sequence(3,"s1"); также создает временный объект и копирует все данные в s1. Теперь s1._name - «s1», s1._count - 3, а s1._content указывает на блок памяти, выделенный для указателя _content объекта temp.

И к этому времени у вас будет:

destructor ---abc  // first temp object
-572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -57
2662307 -572662307 -572662307  // first s1.show()
destructor ---s1  // second temp object

По той же причине, 2-й s1.show() также дает вам данные для мусора, но с количеством = 3.

Когда все это будет сделано, в конце основной функции объект s1 будет уничтожен. И это приведет к тому, что вы пытаетесь удалить память, которая уже освобождена (уже удалена в деструкторе 2-го временного объекта).

Причиной, по которой вы увидели другой вывод из моего, может быть то, что ваш компилятор "достаточно умен", чтобы исключить создание временного объекта с конструктором копирования по умолчанию.

Надеюсь, это поможет.

1 голос
/ 07 августа 2011
Sequence s1 = Sequence();

Создает два Sequence объекта.Первый создан Sequence().Второй создается (путем копирования-построения) Sequence s1.Или, другими словами, этот эквивалент:

const Sequence &temp = Sequence();
Sequence s1 = temp;

Sequence s1 не создает ссылку на объект.Создает объект .Полностью сформирован.Вы можете сделать:

Sequence s1;
s1.show();

И это прекрасно.

Если вы хотите вызвать конструктор не по умолчанию, просто сделайте это:

Sequence s2(3,"s1");

Чтобы понятьОткуда возникла проблема, посмотрите на эту версию:

const Sequence &temp = Sequence();
Sequence s1 = temp;

Вы создаете Sequence объект.Это заставляет конструктор выделять массив с new.Fine.

Вторая строка принимает временный объект Sequence и копирует в s1.Это называется «назначение копирования».

Поскольку вы не определили оператор назначения копирования, это означает, что C ++ будет использовать алгоритм копирования по умолчанию.И это просто байтовая копия (она также запускает назначение копии членам класса).Таким образом, вместо того, чтобы Sequence вызывать свой конструктор, он получает данные, скопированные в него из временного temp.

Вот проблема.В исходном коде временный, который вы создаете с помощью Sequence()? уничтожается , когда это утверждение заканчивается.Он существует достаточно долго, чтобы его содержимое можно было скопировать в s1, затем он уничтожен .

Уничтожение означает, что вызывается его деструктор.Его деструктор удалит массив.

Теперь подумайте о том, что произошло.Временный появился и выделил массив. указатель на этот массив был скопирован в s1.Затем временный объект был уничтожен, в результате чего массив был освобожден.

Это означает, что s1 теперь содержит указатель на освобожденный массив .Вот почему голые указатели плохи в C ++.Вместо этого используйте std::vector.

Кроме того, не используйте инициализацию копирования подобным образом.Если вы просто хотите Sequence s1, создайте его просто:

Sequence s1;
1 голос
/ 07 августа 2011

Строка:

Sequence s1 = Sequence();

Создает временный объект и, используя конструктор копирования Sequence, копирует его в s1.Затем он вызывает деструктор временного.Поскольку у вас не написан конструктор копирования, байты членов анонимного объекта копируются в новый, то есть s1.Затем временный объект выходит из области видимости и вызывается деструктор.Деструктор печатает имя и удаляет память, которой также владеет s1, поэтому теперь s1 владеет некоторой deleted[] редактируемой памятью.

Затем вы делаете

s1 = Sequence(3,"s1");

, которая используетоператор присваивания анонимного Sequence для s1.Здесь снова анонимный объект выходит из области видимости и вызывается деструктор, а s1 по-прежнему владеет указателем на уничтоженную память.

Чтобы решить эту проблему, вам нужно определить конструктор копирования и присваиваниеоператор:

Sequence::Sequence(const Sequence& rhs) : _name(rhs._name), _count(rhs._count), _content(new int[_count]) {
    for (int i = 0; i < _count; ++i)
        _content[i] = rhs._content[i];
}

Sequence& operator=(const Sequence& rhs) {
    if (&rhs != this) {
        delete[] _content;
        _count = rhs._count;
        _name = rhs._name;

        _content = new int[_count];

        for (int i = 0; i < _count; ++i)
            _content[i] = rhs._content[i];
    }

    return *this;
}

Причина в том, что при создании копии Sequence новый Sequence не должен делать копию указателя, который содержал старый Sequence (и укажите на тот же блок памяти), но создайте для себя новый блок памяти и скопируйте все данные из старого блока памяти Sequence s в новый.

Возможно, существует несколько новых концепцийдля вас в этом коде, так что изучите его некоторое время и задавайте вопросы, когда вы чего-то не понимаете.

...