简体   繁体   中英

Is there any way to avoid memory leak in the following code?

#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);
}

Please attention to for(auto j {0}; j < i; ++j) in catch(...) {} .

In this code, I can change the for 's condition to j <= i to avoid memory leak. But if p is in a template container, the revision before may cause UB. Because p + i hasn't been constructed completely, destructing it directly will cause undefined behaviour.

Is there any way to avoid it, or it is a responsibility for class designer?

If this is some interview question, please tell the interviewer that you don't write code like that, then you get the job.

If this is some homework, please, give your teacher the following link so he can learn something: https://www.aristeia.com/EMC++.html

Finally, answering your question:

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
    }
}

Now, actually answering your question and making your code even more contrived, you can use a constructor initializer list try-catch (more here )

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;
};

The main being the same as yours.

As for the memory leak and undefined behavior:

  1. ::operator delete (p); does not destroy the objects you manually created in the allocated storage.

  2. If two exceptions are thrown in the copy constructors, you will try to delete the same object multiple times.

  3. The for loop in the catch block should not leak memory. If a constructor throws it is supposed to be in the uninitialized state afterwards. In other words if new int throws, no space will have been allocated that needs freeing. Your manual exception throwing requires you to assure that the new int allocation is deleted again before the constructor throws.

  4. All of the code you are trying to write in main is basically reinventing what Foo* p = new Foo[5]{f}; would do and the memory managment in the class would be working automatically, even if you are throwing from the constructor if you used std::unique_ptr<int> instead of int* .

This has nothing to do with whatever happens or doesn't happen in main(). In the constructor:

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

Because the object never finishes constructing, its destructor never gets called. You cannot destroy something unless it's been constructed first, and you're throwing an exception before the object finishes constructing.

But because a member of the class was constructed with new , and because the destructor does not get called, that's where the memory leak occurs.

The only practical way to avoid leaking memory here is to either manually delete x before throwing this exception, and cleaning up what you allocated; or make x an object that's responsible for cleaning itself up, like a unique_ptr , and its destructor will take care of it, when the exception gets thrown. This would be more in line with the RAII principle .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM