Hexa
Hexa

Reputation: 27

What Happens To Dynamically Allocated Variables Without Handles ? (C++)

Does instantiating classes without handles like this cause memory leaks in C++?

new SomeClass();

What about passing them inside methods?

SomeMethod(new SomeClass())

Do they get deallocated after the method's definition goes out of scope?

It does sound like a stupid question, but as far as I know, they're not going anywhere if they aren't freed.

Upvotes: 2

Views: 127

Answers (1)

Henrique Bucher
Henrique Bucher

Reputation: 4474

Yes, you are correct. In default C++, every new call must be followed by a delete, otherwise it is a leak. However there are edge cases where new can be used without a delete.

One such case is with placement new where memory allocation is handled by yourself. Example:

#include <cstdint>
#include <memory>
#include <iostream>

struct X {
    int a;
    int b;
};

int main(int argc, char** argv) {

    char buffer[256];

    X* x1 = new (&buffer[0]) X{1,2};
    X* x2 = new (&buffer[8]) X{3,4};

    std::cout << "x1: " << x1->a << " " << x1->b << std::endl;
    std::cout << "x2: " << x2->a << " " << x2->b << std::endl;

    // no leaks since the memory is released when `buffer` is 
    // deallocated. However it is good practice to call the 
    // destructor directly
    x1->~X();
    x2->~X();
}

Produces:

Program stdout
x1: 1 2
x2: 3 4

Godbolt: https://godbolt.org/z/sEPWadcKh

Another case is when a class overrides the operator new as in this example:

#include <cstdint>
#include <memory>
#include <iostream>

static char buffer[32768];
static char* ptr = &buffer[0];

struct X {
    int a;
    int b;
    void* operator new(size_t size) {
        void* p = ptr;
        ptr += size;
        return p;
    }
    void operator delete(void*) {
        // no need to do anything
    }
};

int main(int argc, char** argv) {

    X* x1 = new X{1,2};
    X* x2 = new X{3,4};

    std::cout << "x1: " << x1->a << " " << x1->b << std::endl;
    std::cout << "xs: " << x2->a << " " << x2->b << std::endl;

    // no leaks since the memory is released when `buffer` is 
    // deallocated. However it is good practice to call the 
    // destructor directly
    x1->~X();
    x2->~X();
}

Produces:

Program stdout
x1: 1 2
x2: 3 4

Godbolt: https://godbolt.org/z/jc4r9qbxh

Yet another case is when you use new with smart pointers like in the case below

#include <cstdint>
#include <memory>
#include <iostream>
#include <boost/intrusive_ptr.hpp>

struct X {
    X(int a_, int b_ ) : a(a_), b(b_){
        std::cout << "Constructor " << this << std::endl;
    }
    ~X() {
        std::cout << "Destructor " << this << std::endl;
    }
    int a;
    int b;
    int count = 0;
};

void intrusive_ptr_add_ref(X* x)
{
    ++x->count;
}

void intrusive_ptr_release(X* x)
{
    if (--x->count == 0)
        delete x;
}

int main(int argc, char** argv) {

    boost::intrusive_ptr<X> x1 = new X{1,2};
    boost::intrusive_ptr<X> x2 = new X{3,4};

    std::cout << "x1: " << x1->a << " " << x1->b << std::endl;
    std::cout << "x2: " << x2->a << " " << x2->b << std::endl;
}

Produces:

Constructor 0xb4b2b0
Constructor 0xb4c2e0
x1: 1 2
x2: 3 4
Destructor 0xb4c2e0
Destructor 0xb4b2b0

Godbolt: https://godbolt.org/z/b7MTfazT9

So although delete is called on your behalf, you as in the user does not have to call delete yourself.

Yet another use case is when creating objects for frameworks that manage the lifetime of objects like Qt.

So the answer is really YES, always call delete after new but there are plenty of cases in the industry where it is not really necessary.

Upvotes: 3

Related Questions