2

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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Hexa
  • 27
  • 4
  • 1
    Each `new` must be followed by `delete`, or you get a leak. What you showed doesn't change this. – HolyBlackCat Jan 28 '23 at 03:10
  • 1
    `Does instantiating classes without handles like this cause memory leaks in C++ ?` Yes. `Do they get deallocated after the method's definition goes out of scope ?` No. If __you__ don't `delete` them, they're not going to be free'd. – tkausl Jan 28 '23 at 03:13
  • 1
    @HolyBlackCat Not really. A placement new does not necessarily leak. Also if the object has overridden the new operator and handles it with a garbage collector it also does not leak. – Something Something Jan 28 '23 at 03:21
  • 1
    @NoleKsum Yes, but OP doesn't need to know this. When they eventually study placement-new, they will be told that it's an exception from this rule. – HolyBlackCat Jan 28 '23 at 03:23
  • https://stackoverflow.com/questions/677812/is-there-a-reason-to-call-delete-in-c-when-a-program-is-exiting-anyway – Drew Dormann Jan 28 '23 at 03:26
  • 1
    In modern C++ you (almost) never call `new`. If you want an automatic stack variable, just declare it (and initialize it): `Someclass sc;`. If you want a dynamic variable you use `std::unique_ptr`: (`auto scP = std::make_unique()`). – alfC Jan 28 '23 at 03:49
  • Also note that new/delete may not even be necessary. And you should look for other solutions before typing new/delete. Like storing objects in std::vector. If you need dynamic memory allocation use std:::unique_ptr/std::make_unique. Those pointers will automatically be deleted when they go out of scope. – Pepijn Kramer Jan 28 '23 at 07:14
  • @alfC "Modern C++" does not exist. It is a buzzword that keeps being flaunted around by snub types. Up to the latest standard C++23 the operator `new` is an integral part of the C++ language, period. All the rest is 100% personal preference. – Something Something Jan 28 '23 at 16:19
  • @NoleKsum. There is a logic behind avoiding `new/delete`. This is enabled by `std::unique_ptr` which is also an integral part of the language. A part of the language that was not available before. Now call this whatever you want. “In a C++-that-was-not-available-before-but-is-available-now we typically not call new”. seems a mouthful. – alfC Jan 28 '23 at 20:52

1 Answers1

3

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.

Something Something
  • 3,999
  • 1
  • 6
  • 21
  • is there such thing as “in default C++”? do you mean in “correct programs that don’t leak”? – alfC Jan 28 '23 at 20:58