1

I keep getting a Memory Leak which seems to occur because the new initialized objects passed as pointers to add(…) are not actually delete. But somehow, I cannot make this work properly. I tried adding a for-loop to loop every object in array within class Monsters to delete each object when the call of the destructor is invoked. And then to do delete[] monsters; and monsters = nullptr.

https://repl.it/@xxxxxx10/TatteredTroubledTasks


#include <iostream>
#include <string>

class Monster{
public:
  Monster(){};
  Monster(std::string name) : name(name){}
private:
  std::string name = "";
};

class MonstersList {
public:
  MonstersList(int max):max(max){
    monsters = new Monster[max];
  }
  
  virtual ~MonstersList() {
      delete[] monsters;
  }

  void add(const Monster* m){
    monsters[total++] = *m;
  }

 Monster* begin() const { return monsters; }
 Monster* end() const { return monsters + total; }

private:
  Monster* monsters = nullptr;
  int max = 0;
  int total = 0;
};

MonstersList* InitListOfMonsters()
{
    MonstersList* monsters = new MonstersList(2);
    monsters->add(new Monster("Sally"));
    monsters->add(new Monster("Rich"));
    
    return monsters;
}

int main()
{
  MonstersList* monsters = InitListOfMonsters();
  
  // Do something here...

  return 0;
} // <----- Memory leak!

How can this be prevented from happening, recall, I tried to for loop and delete each monster pointer object in the array.

EDIT: When I try for-loop to delete each object, then an error sort of occurs, breaks at delete_scalar.cpp <-- Microsoft C++ file.

_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block) noexcept
{
    #ifdef _DEBUG
    _free_dbg(block, _UNKNOWN_BLOCK);
    #else
    free(block);
    #endif
}

Occurs when I run this code in destructor:

if (monsters != nullptr) {
    for (Monster* m = begin(); m != end(); m++) {
        delete m;
    }

    delete[] monsters;
    monsters = nullptr;
}
  • `std::unique_ptr monsters = InitListOfMonsters();` – Ted Lyngmo Oct 03 '20 at 09:26
  • @TedLyngmo, I know about `std::unique_ptr`, but, I am doing this the old school way. –  Oct 03 '20 at 09:28
  • @OlafDietsche, I been Googling and trying things out for days. It does compile in my Visual Studio. –  Oct 03 '20 at 09:31
  • 1
    `MonsterList` != `Monsters` != `Monster`, if this code really compiles in Visual Studio (I don't have it available to test), then you should use a more reliable tool. – Olaf Dietsche Oct 03 '20 at 09:33
  • 2
    @AlbinM *but, I am doing this the old school way.* -- The "old school" way requires you to know about user-defined copy constructors, assignment operators, etc. Just using `new[]` and `delete[]` teaches very little. To actually fix your classes, it requires much more effort than you've led to believe. – PaulMcKenzie Oct 03 '20 at 09:34
  • Your `add` function calls already leaks memory, as you pass in a pointer to dynamically allocated memory, copy the pointed to object and then discard the pointer, never freeing the memory it referred to – UnholySheep Oct 03 '20 at 09:35
  • @UnholySheep, I didn't know that. –  Oct 03 '20 at 09:36
  • @OlafDietsche, I updated the entire code here. –  Oct 03 '20 at 09:37
  • And your loop with `delete m;` is also completely flawed, as you have an array of `Monster`, not of `Monster*` - you cannot call `delete` on the objects stored in the array – UnholySheep Oct 03 '20 at 09:38
  • @UnholySheep, I thought I could loop over every individual object and delete them. –  Oct 03 '20 at 09:41
  • @AlbinM -- *I thought I could loop over every individual object and delete them.* -- C++ is a complex language. You can't learn it by trial and error, which seems to be how you're going about it. – PaulMcKenzie Oct 03 '20 at 09:42
  • You can only `delete` what you `new` - and the objects in your array have not been `new`d (since they aren't pointers to objects) – UnholySheep Oct 03 '20 at 09:42
  • @UnholySheep, so my function for adding many objects is wrong? –  Oct 03 '20 at 09:44
  • 1
    Yes, your function is wrong. It's also unclear why you are trying to pass a pointer to `add` instead of a reference. Combined with all the other issues in your code you should see why the recommended way to write modern C++ code is to use `std::vector` and smart pointers instead of manually managing such memory (and generally avoiding writing `new` and `delete`). – UnholySheep Oct 03 '20 at 09:47
  • 1
    @AlbinM It's at the point where an answer would more than likely be a total rewrite of what you're doing. This isn't something that can be fixed with a tweak here or there. Like I stated, to get your code to actually work and be usable, given you want to do things the "old school way", would require a lot more work than you had thought. – PaulMcKenzie Oct 03 '20 at 09:50

2 Answers2

4

There are two places, where the code leaks memory

MonstersList* InitListOfMonsters()
{
    // ...
    monsters->add(new Monster("Sally"));
    // ...
    
    return monsters;
}

Each of the news alloctes memory, which is never freed. Since you don't store pointers, but Monster objects, you should not pass pointers, but Monsters.

monsters->add(Monster("Sally"));

and modify add() as

void add(Monster const &m) {
    monsters[total++] = m;
}

The second one I see, is in main

MonstersList* monsters = InitListOfMonsters();

there's no corresponding

delete monsters;

The destructor is not executed until the object is destroyed. This happens, when you call delete monsters.


Update for the EDIT

This code has two issues

if (monsters != nullptr) {
    for (Monster* m = begin(); m != end(); m++) {
        delete m;
    }

    delete[] monsters;
    monsters = nullptr;
}

the loop is equivalent to

for (Monster* m = monsters; m != (monsters + total); m++) {
    delete m;
}

The first delete is delete monsters, which deletes the whole array and causes a double delete with the later delete[] monsters. Furthermore does it delete the following elements, which are not allocated separately, but as part of the whole array (which was already deleted in the first iteration).

The second issue is delete vs delete[]. A simple delete is for single objects, whereas delete[] is for dynamically allocated arrays. In this case the first delete m inside for deletes an array with the non-array delete.


Finally, heed the suggestions and warnings in the comments. When you use C++, especially C++11 and newer, do it the C++ way. Use vector and unique_ptr, if pointers are really necessary.

Olaf Dietsche
  • 72,253
  • 8
  • 102
  • 198
  • *there's no corresponding* that's because I am expecting the **destructor** to be executed. That's why. –  Oct 03 '20 at 09:51
  • There's no destructor for a pointer. See updated answer. – Olaf Dietsche Oct 03 '20 at 09:54
  • Olaf, what if `Monster* m = new Monster("Sally");` and then `monsters->add(m);` would this still be valid, even if `add` takes a reference? –  Oct 03 '20 at 09:56
  • @AlbinM Destructors are not automatic with naked dynamic allocation. Automatic destruction of dynamic allocation requires an automatic watchdog (i.e. *smart pointers* which, if you must use dynamic allocation, should be mandatory, but only after you've determine you even need manual dynamic allocation in the first place, and chances are even-keel whether you do or don't). – WhozCraig Oct 03 '20 at 09:58
  • 2
    @AlbinM You seem to come from other languages where `new` is frequently used or even required to create objects. However that's not the case in C++. In modern C++ you almost never need to (or should) use a naked `new` or naked pointers. Especially when you are new to C++ I'd recommend to _not_ use raw pointers, but to stick with smart pointers and standard containers. – Lukas-T Oct 03 '20 at 10:01
1

Well here's my old school effort. See comments at various points in the code for the things you were missing.

References

What is The Rule of Three?

What is the copy-and-swap idiom?

It is good to know this stuff, although that doesn't mean you should be old school. Modern C++ is better.

#include <iostream>
#include <algorithm>
#include <string>

class Monster
{
public:
    Monster() {};
    Monster(std::string name) : name(name) {}
private:
    std::string name = "";
};

class MonstersList
{
public:
    MonstersList(int max) : monsters(new Monster[max]), max(max), total(0)
    {
    }

    // copy constructor, essential if your destructor deletes, see rule of three
    MonstersList(const MonstersList& rhs) : monsters(new Monster[rhs.max]), max(rhs.max), total(rhs.total)
    {
        std::copy(rhs.begin(), rhs.end(), begin());
    }

    // assignment operator, again essential with a deleting destructor, 
    // this implementation uses the copy and swap idiom
    MonstersList& operator=(MonstersList rhs)
    {
        swap(rhs);
        return *this;
    }

    virtual ~MonstersList()
    {
        delete[] monsters;
    }

    void swap(MonstersList& rhs)
    {
        std::swap(monsters, rhs.monsters);
        std::swap(max, rhs.max);
        std::swap(total, rhs.total);
    }

    void add(const Monster& m) // pass by reference
    {
        monsters[total++] = m;
    }

    Monster* begin() const { return monsters; }
    Monster* end() const { return monsters + total; }

private:
    Monster* monsters;
    int max;
    int total;
};

MonstersList* InitListOfMonsters()
{
    // allocate a list of monsters, but no need to allocate the monsters themselves
    MonstersList* monsters = new MonstersList(2);
    monsters->add(Monster("Sally"));
    monsters->add(Monster("Rich"));

    return monsters;
}

int main()
{
    MonstersList* monsters = InitListOfMonsters();
    // Do something here...

    // don't forget to delete the monster list
    delete monsters;
    return 0;
}
john
  • 85,011
  • 4
  • 57
  • 81
  • `monsters->add({"Sally"}); monsters->add({Rich"});` -- Looks a little nicer, IMO. – PaulMcKenzie Oct 03 '20 at 10:06
  • @PaulMcKenzie yes looks good, I still have to get used to uniform initialisation – john Oct 03 '20 at 10:08
  • @john, this wouldn't work with Monster* m = new Monster("Sally"); and then monsters->add(m); –  Oct 03 '20 at 10:16
  • @AlbinM No it wouldn't, but there is no reason to allocate the Monsters objects when your MonsterList class stores Monsters not Monster pointers. So the code above is an improvement. If you want to allocate Monster objects as well then you need to change your MonsterList class to store Monster pointers. – john Oct 03 '20 at 10:36
  • 1
    @AlbinM -- As another comment suggested, it seems you have the misconception that C++ requires `new` to create objects. This is not the case. – PaulMcKenzie Oct 03 '20 at 10:41
  • @AlbinM For example `Monster m("Sally");` and then `monsters->add(m);` – john Oct 03 '20 at 10:44