Есть ли способ избежать утечки памяти в следующем коде? - PullRequest
0 голосов
/ 17 ноября 2018
#include <new>

class Foo {
public:
    int *x;
    mutable size_t count {1};
    Foo() : x {new int}  {}
    Foo(const Foo &rhs) : x {new int} {
        if(++rhs.count > 5) {
            throw runtime_error("");
        }
    }
    ~Foo() {
        delete x;
    }
    Foo &operator=(const Foo &) = delete;
};
int main(int argc, char *argv[]) {
    Foo *p {reinterpret_cast<Foo *>(::operator new (sizeof(Foo) * 5))};
    Foo f;
    for(auto i {0}; i < 5; ++i) {
        try {
            new (p + i) Foo(f);
        }catch(...) {
            for(auto j {0}; j < i; ++j) {    //!
                (p + j)->~Foo();
            }
        }
    }
    ::operator delete (p);
}

Обратите внимание на for(auto j {0}; j < i; ++j) в catch(...) {}.

В этом коде я могу изменить условие for на j <= i, чтобы избежать утечки памяти.Но если p находится в контейнере шаблона, ревизия до может вызвать UB.Поскольку p + i не был создан полностью, его непосредственное уничтожение вызовет неопределенное поведение.

Есть ли способ избежать этого или это является обязанностью дизайнера классов?

Ответы [ 3 ]

0 голосов
/ 17 ноября 2018

Что касается утечки памяти и неопределенного поведения:

  1. ::operator delete (p); не уничтожает объекты, созданные вами вручную в выделенном хранилище.

  2. Если в конструкторы копирования выдается два исключения, вы попытаетесь удалить один и тот же объект несколько раз.

  3. Цикл for в блоке перехвата не должен пропускать память. Если конструктор выдает его, он должен быть в неинициализированном состоянии после этого. Другими словами, если new int сгенерирует, не будет выделено место, которое необходимо освободить. Ручная выдача исключений требует, чтобы вы гарантировали, что распределение new int снова будет удалено до того, как сработает конструктор.

  4. Весь код, который вы пытаетесь написать в main, в основном заново изобретает то, что будет делать Foo* p = new Foo[5]{f};, и управление памятью в классе будет работать автоматически, даже если вы выкидываете из конструктора, если вы используется std::unique_ptr<int> вместо int*.

0 голосов
/ 17 ноября 2018

Это не имеет никакого отношения к тому, что происходит или не происходит в main ().В конструкторе:

    if(++rhs.count > 5) {
        throw runtime_error("");

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

Но потому что член класса был создан с new, и потому что деструктор не получаетВ этом случае происходит утечка памяти.

Единственный практический способ избежать утечки памяти - это либо вручную delete x перед выбрасыванием этого исключения, либо очистить выделенное;или сделайте x объектом, ответственным за очистку, таким как unique_ptr, и его деструктор позаботится об этом, когда возникнет исключение.Это было бы больше в соответствии с принципом RAII .

0 голосов
/ 17 ноября 2018

Если это какой-то вопрос на собеседовании, скажите интервьюеру, что вы не пишете подобный код, тогда вы получите работу.

Если это домашнее задание, дайте вашему учителю следующую ссылкучтобы он мог чему-то научиться: https://www.aristeia.com/EMC++.html

Наконец, отвечая на ваш вопрос:

int main(int argc, char *argv[]) {
    std::unique_ptr<Foo> p[5];
    Foo f;
    try {
        for (int i=0;i<5;++i) {
            //p[i]=std::make_unique<Foo>(f); //Only for C++14
            p[i]=std::unique_ptr<Foo>(new Foo(f));
        }
    } catch (...) {
        //Nothing, all is done "magically" by unique_ptr
    }
}

Теперь, на самом деле, отвечая на ваш вопрос и делая ваш код еще более надуманным, вы можете использоватьtry-catch списка инициализаторов конструктора (подробнее здесь )

class Foo {
public:
    int *x;
    mutable size_t count {1};
    Foo() : x {new int}  {}
    Foo(const Foo &rhs) try: x {new int} {
        if(++rhs.count > 5) {
            throw runtime_error("");
        }
    } catch (...) {
        delete x;
        throw;
    }
    ~Foo() {
        delete x;
    }
    Foo &operator=(const Foo &) = delete;
};

Основное - то же, что и ваше.

...