перегрузить оператор удаления, или как убить кота? - PullRequest
4 голосов
/ 21 декабря 2009

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

Я определяю класс Cat, который создан с несколькими душами, имеет перегруженный оператор delete, который ничего не делает, и деструктор, который уменьшает количество душ (а также немного хвастается). Когда souls достигает 0, деструктор вызывает global :: delete, и кот умирает.

Звучит довольно просто, но работает не так, как ожидалось. Вот код:

class Cat {
public:
    Cat(string n): name(n), souls(9)
    { cout << "Myaou... " << name << " is born\n"; }

    ~Cat();
    void operator delete(void *p) { cout << "!!! operator delete called\n"; }
    void report()
    { cout << name << "'s here, " << souls << " souls to spend\n"; }

    friend ostream& operator<< (const ostream& o, const Cat& cat);
private:
    void kill();
    const string name;
    int souls;
};

Cat::~Cat()
{
    cout << "!!! dtor called\n";
    kill();
}

void Cat::kill()
{
    if (--souls)
        cout << name << " is still alive! I have " << souls << " souls left.\n";
    else {
        cout << name << " is dying... good bye world!\n";
        ::delete((void*)this);
    }
}

ostream& operator<< (const ostream& o, const Cat& cat)
{
    return o << cat.name << "'s here, " << cat.souls << " souls to spend\n";
}

вот главное:

int main()
{
    Cat *p = new Cat("Mitzi");

    for (;;)
    {
        char c[100];
//      cout << *p;
        p->report();
        cout << "come on, hit me!";
        cin >> c;
        delete p;
    }
}

Я ожидаю, что цикл будет выполняться 9 раз, а затем произойдет что-то неприятное (аварийное завершение). Тем не менее, это вывод:

Myaou... Mitzi is born
Mitzi's here, 9 souls to spend
come on, hit me!c
!!! dtor called
Mitzi is still alive! I have 8 souls left.
!!! operator delete called
's here, 8 souls to spend
come on, hit me!c
!!! dtor called
 is still alive! I have 7 souls left.
*** glibc detected *** /home/davidk/workspace/string_test/Debug/string_test: double free or corruption (fasttop): 0x080cd008 ***

Похоже, что после первого удаления имя элемента уничтожается, а следующее удаление вызывает сбой. Есть объяснения? Я компилирую с gcc на Linux, может быть ошибка компилятора?

Кстати, когда я использовал оператор << () как в cout << * p вместо repotr (), это также было странно: он входил в бесконечный цикл вызова конструктора из оператора << (). Что тут происходит? :) </p>

спасибо!

Ответы [ 5 ]

19 голосов
/ 21 декабря 2009

Вы неправильно использовали все возможные концепции C ++, и это вызывает ошибки. Когда delete p; вызывается впервые, C ++ вызывает деструктор Cat::~Cat(), который неявно уничтожает std::string внутри сущности. Когда delete p; вызывается во второй раз, Cat::~Cat() перезапускается, и он перезапускает деструктор std::string, и это вызывает неопределенное поведение, вызывающее сбой программы, потому что я полагаю, std::string::~string() не отменяет указатель на буфер и поэтому std::string::~string() пытается освободить буфер во второй раз с тем же адресом, что приводит к известному двойному освобождению.

7 голосов
/ 21 декабря 2009

оператор delete вызывает деструктор объекта, и после этого вы не находитесь на чужой земле. Как указывали другие, то, что вы пытаетесь сделать, невозможно.

То, что вы делаете, также немного хитроумно в отношении противоречивого поведения, когда объект создается в куче и в стеке.

С вашей идеей переопределить оператор delete объект останется живым в зависимости от некоторой внутренней логики (количество жизней достигло 9), если он построен с использованием нового.

При построении в стеке (Cat cat("Chesire cat");) объект будет всегда разрушаться при выходе из области видимости. Чтобы достичь того, что вы пытаетесь сделать, вам также нужно изменить поведение деструктора, чтобы он прекратил разрушать. Это невозможно, также по очень веским причинам.

Итак, если вы хотите подсчитывать реф, просто реализуйте свой собственный механизм. В конце концов, вы не можете называть себя программистом на C ++, если вы хотя бы раз не выполнили свое собственное управление памятью подсчета ссылок)

2 голосов
/ 21 декабря 2009

После вызова деструктора объекта все, что вы делаете с объектом, не определено. То, что вы пытаетесь сделать, невозможно.

1 голос
/ 21 декабря 2009

Вы должны знать, что вы не можете выполнить удаление по одному адресу более одного раза.

1 голос
/ 21 декабря 2009

Деструктор не отвечает за освобождение памяти, и вы не препятствовали этому.

Первый звонок освобождает память, второй звонок взрывается.

...