-1

I am having an issue emptying my set, so I have 3 classes like so:
class A, and 2 inherited classes B and C. In the code I store elements in my set from the 3 types, the set is:

set<A*> objects;

so whenever I create a B element I do that:

A* b = new B(); // calling B C'tor 

// and so on with A and C elements I do the exact same.`

So here comes the problem, whenever I want to erase an element, or even end the program (which calls the destructor), I don't know what I should type in the destructor, I have it like that:

set<A*>::iterator first = objects.begin();
set<A*>::iterator last = objects.end();
while (first != last) {
    set<A*>::iterator to_delete = first;
    objects.erase(to_delete);
    delete *to_delete;    
    ++first;
}

I have also tried putting the delete *to_delete; above objects.erase , also tried putting it alone ,and tried putting the erase alone without delete, but the thing is I have used new so I should use delete somewhere. all things aren't working, the program just crashes with that, I tried keeping the D'tor empty, the program works, but I have a lot of memory leaks, I have checked that.

Please help me, I stuck with this thing. Thank you very much <3

The file: Everything works perfectly except for the Destructor, and the removeRoom function (basically where is a delete.. also operator<< isn't working properly but I believe it's because of that thing, again I have virtual ~EscapeRoomWrapper();

#include <string>
#include <iostream>
#include <set>
#include "Company.h"
#include "ScaryRoom.h"
#include "KidsRoom.h"
#include "Exceptions.h"

using std::set;
using std::endl;

using namespace mtm;
using namespace escaperoom;

Company::Company(string name, string phoneNumber) : name(name), phoneNumber(phoneNumber) {

}

Company::~Company() {
    while(!rooms.empty()) {
        set<EscapeRoomWrapper*>::iterator iter = rooms.begin();
        rooms.erase(iter);
        delete *iter;
    }
//  set<EscapeRoomWrapper*>::iterator first = rooms.begin();
//  set<EscapeRoomWrapper*>::iterator last = rooms.end();
//  while (first != last) {
//      set<EscapeRoomWrapper*>::iterator to_delete = first;
//      rooms.erase(to_delete);
//      delete *to_delete;
//
//      ++first;
//      last = rooms.end();
//  }
//  while (rooms.begin() != rooms.end()) {
//      set<EscapeRoomWrapper*>::iterator to_delete = rooms.begin();
//      rooms.erase(to_delete);
//      delete *to_delete;
//  }
}

Company::Company(const Company& company) : name(company.name), phoneNumber(company.phoneNumber), rooms(company.rooms) {

}

Company& Company::operator=(const Company& company) {
    if (this == &company) {
        return *this;
    }
    name = company.name;
    phoneNumber = company.phoneNumber;
    rooms.clear();
    rooms = company.rooms;
    return *this;
}

void Company::createRoom(char* name, const int& escapeTime, const int& level, const int& maxParticipants) {
    try {
        EscapeRoomWrapper* room = new EscapeRoomWrapper(name, escapeTime, level, maxParticipants);
        rooms.insert(room);
    } catch (EscapeRoomMemoryProblemException& e) {
        throw CompanyMemoryProblemException();
    }
}

void Company::createScaryRoom(char* name, const int& escapeTime, const int& level,
                                 const int& maxParticipants, const int& ageLimit, const int& numOfScaryEnigmas) {
    try {
        EscapeRoomWrapper* room = new ScaryRoom(name, escapeTime, level, maxParticipants, ageLimit, numOfScaryEnigmas);
        rooms.insert(room);
    } catch (EscapeRoomMemoryProblemException& e) {
        throw CompanyMemoryProblemException();
    }
}

void Company::createKidsRoom(char* name, const int& escapeTime, const int& level,
        const int& maxParticipants, const int& ageLimit) {
    try {
        EscapeRoomWrapper* room = new KidsRoom(name, escapeTime, level, maxParticipants, ageLimit);
        rooms.insert(room);
    } catch (EscapeRoomMemoryProblemException& e) {
        throw CompanyMemoryProblemException();
    }
}

set<EscapeRoomWrapper*> Company::getAllRooms() const {
    return rooms;
}

void Company::removeRoom(const EscapeRoomWrapper& room) {
    set<EscapeRoomWrapper*>::iterator first = rooms.begin();
    set<EscapeRoomWrapper*>::iterator last = rooms.end();
    while (first != last) {
        if (**first == room) {
            break;
        }
        ++first;
    }
    if (first == last) {
        throw CompanyRoomNotFoundException();
    }
    delete *first;
    rooms.erase(first);
   // delete *first;     // check this
}

void Company::addEnigma(const EscapeRoomWrapper& room, const Enigma& enigma) {
    set<EscapeRoomWrapper*>::iterator first = rooms.begin();
    set<EscapeRoomWrapper*>::iterator last = rooms.end();
    while (first != last) {
        if (**first == room) {
            break;
        }
        ++first;
    }
    if (first == last) {
        throw CompanyRoomNotFoundException();
    }
    (**first).addEnigma(enigma);
}

void Company::removeEnigma(const EscapeRoomWrapper& room, const Enigma& enigma) {
    set<EscapeRoomWrapper*>::iterator first = rooms.begin();
    set<EscapeRoomWrapper*>::iterator last = rooms.end();
    while (first != last) {
        if (**first == room) {
            break;
        }
        ++first;
    }
    if (first == last) {
        throw CompanyRoomNotFoundException();
    }
    try {
        (**first).removeEnigma(enigma);
    } catch (EscapeRoomNoEnigmasException& e) {
        throw CompanyRoomHasNoEnigmasException();
    } catch (EscapeRoomEnigmaNotFoundException& e) {
        throw CompanyRoomEnigmaNotFoundException();
    }
}

void Company::addItem(const EscapeRoomWrapper& room, const Enigma& enigma, const string& element) {
    set<EscapeRoomWrapper*>::iterator first_room = rooms.begin();
    set<EscapeRoomWrapper*>::iterator last_room = rooms.end();
    while (first_room != last_room) {
        if (**first_room == room) {
            break;
        }
        ++first_room;
    }
    if (first_room == last_room) {
        throw CompanyRoomNotFoundException();
    }
    vector<Enigma>::iterator first = (**first_room).getAllEnigmas().begin();
    vector<Enigma>::iterator last = (**first_room).getAllEnigmas().end();
    while (first != last) {
        if (*first == enigma) {
            break;
        }
        ++first;
    }
    if (first == last) {
        throw CompanyRoomEnigmaNotFoundException();
    }
    (*first).addElement(element);
}

void Company::removeItem(const EscapeRoomWrapper& room, const Enigma& enigma, const string& element) {
    set<EscapeRoomWrapper*>::iterator first_room = rooms.begin();
    set<EscapeRoomWrapper*>::iterator last_room = rooms.end();
    while (first_room != last_room) {
        if (**first_room == room) {
            break;
        }
        ++first_room;
    }
    if (first_room == last_room) {
        throw CompanyRoomNotFoundException();
    }
    vector<Enigma>::iterator first = (**first_room).getAllEnigmas().begin();
    vector<Enigma>::iterator last = (**first_room).getAllEnigmas().end();
    while (first != last) {
        if (*first == enigma) {
            break;
        }
        ++first;
    }
    if (first == last) {
        throw CompanyRoomEnigmaNotFoundException();
    }
    try {
        (*first).removeElement(element);
    } catch (EnigmaNoElementsException& e) {
        throw CompanyRoomEnigmaHasNoElementsException();
    } catch (EnigmaElementNotFoundException& e) {
        throw CompanyRoomEnigmaElementNotFoundException();
    }
}

set<EscapeRoomWrapper*> Company::getAllRoomsByType(RoomType type) const {
    set<EscapeRoomWrapper*> filtered_set;
    set<EscapeRoomWrapper*>::iterator first_room = rooms.begin();
    set<EscapeRoomWrapper*>::iterator last_room = rooms.end();
    EscapeRoomWrapper* ptr = NULL;
    while (first_room != last_room) {
        if (type == BASE_ROOM) {
            if (dynamic_cast<ScaryRoom*>(*first_room) == ptr
                    && dynamic_cast<KidsRoom*>(*first_room) == ptr) {
                filtered_set.insert(*first_room);
            }
        }
        if (type == SCARY_ROOM) {
            if (dynamic_cast<ScaryRoom*>(*first_room) != ptr) {
                filtered_set.insert(*first_room);
            }
        }
        if (type == KIDS_ROOM) {
            if (dynamic_cast<KidsRoom*>(*first_room) != ptr) {
                filtered_set.insert(*first_room);
            }
        }
        ++first_room;
    }
    return filtered_set;
}

EscapeRoomWrapper* Company::getRoomByName(const string& name) const {
    set<EscapeRoomWrapper*>::iterator first_room = rooms.begin();
    set<EscapeRoomWrapper*>::iterator last_room = rooms.end();
    while (first_room != last_room) {
        if ((**first_room).getName() == name) {
            break;
        }
        ++first_room;
    }
    if (first_room == last_room) {
        throw CompanyRoomNotFoundException();
    }
    return *first_room;
}

std::ostream& mtm::escaperoom::operator<<(std::ostream& output, const Company& company) {
    output << company.name << " : " << company.phoneNumber << endl;
    set<EscapeRoomWrapper*>::iterator first_room = company.getAllRooms().begin();
    set<EscapeRoomWrapper*>::iterator last_room = company.getAllRooms().end();
    while (first_room != last_room) {
        output << **first_room << endl;
        ++first_room;
    }
    return output;
}
einpoklum
  • 118,144
  • 57
  • 340
  • 684
ConanCoder
  • 39
  • 2
  • 10
  • Possible duplicate of [Memory Leaks - STL sets](https://stackoverflow.com/questions/2394529/memory-leaks-stl-sets) – YLJ Jul 01 '17 at 10:35
  • I know you've already gotten some answers, including my own - but your question could stand to have the example shortened. – einpoklum Jul 01 '17 at 19:27

4 Answers4

2

The key problem with your approach is the fact that your modifying your container while iterating over it. I'd suggest to refactor it to:

while (!objects.empty()) {
     set<A*>::iterator it = objects.begin();
     objects.erase(it);
     delete *it;
}

Alternatively you can do something like this with C++11 and lamdas:

std::for_each(objects.begin(), objects.end(), [](A* obj){ delete obj; });
objects.clear();


Just tested on simplified version based on your description, following snippet works for me pretty well:
#include <iostream>
#include <set>

using namespace std;


class A {
};

class B : public A {
};

int main(int argc, const char *argv[])
{
    set<A*> objects;

    for (int i = 0; i < 10; i++) {
        objects.insert(new B());
    }

    for(set<A*>::iterator it = objects.begin(); it != objects.end(); ++it) {
        delete *it;
    }
    objects.clear();
    cout << "Hello World" << endl;
    return 0;
}

I would suspect we missing some details here.


UPDATE

Ok, while it's hard to see the whole picture of what are you trying to do here as most of the details are still missing, I spotted one potential problem with copy constructor. In your updated code you are doing shallow copy of the Company object, but I think that you meant to do is:

Company& Company::operator=(const Company& company) {
    if (this == &company) {
        return *this;
    }
    name = company.name;
    phoneNumber = company.phoneNumber;
    // Also clear might be not enough since you also need to release heap memory

    //rooms.clear();

    while (!rooms.empty()) {
       set<A*>::iterator it = rooms.begin();
       rooms.erase(it);
       delete *it;
    }

    // Deep copy of set of rooms in company object
    for (set<Room*>::iterator it = company.rooms.begin(); it != company.rooms.end(); ++i ) {
       rooms.insert(new Room(*it));
    }
    return *this;
}
Artem Barger
  • 40,769
  • 9
  • 59
  • 81
  • I have tried that, it still crashes, I tried debugging, it stops on the `delete *it` line, like omg this is driving me nuts .. :( – ConanCoder Jul 01 '17 at 10:41
  • Can you probably share your code with classes A, B and C? – Artem Barger Jul 01 '17 at 11:08
  • Added the file, please take a look at it, removeRoom and D'tor functions <3 – ConanCoder Jul 01 '17 at 11:17
  • okay so here is the thing, I've fixed it, apparently the D'tor and the remove wasn't the wrong thing, IT WAS THE copy constructor and the opreator= it seems that when I want to copy or assign, I have to erase the memory then allocate it again for the new copy/assign object, that's what was causing the problem, how do I do that with std::set ?? – ConanCoder Jul 01 '17 at 11:50
1

The problem is that objects.end() changes when something is removed from the set and the value stored in last is invalidated.
You can fix your code as follows:

    while (std::begin(objects) != std::end(objects)) {
        set<A*>::iterator to_delete = objects.begin();
        objects.erase(to_delete);
        delete *to_delete;
    }

In general you shouldn't use raw pointers at all in the set. Rather use something like

std::set<std::unique_ptr<A>> objects;

in your program. So you don't need to care for the correct deallocation of the objects at all.

  • Tried that man, still get crash on `delete *to_delete;` ,when I debug it shows it crashes here :( – ConanCoder Jul 01 '17 at 10:57
  • @ConanCoder Again: Did you declare `A`, `B` and `C` destructor functions `virtual`? Otherwise provide a [MCVE] in your question that reproduces the problem. –  Jul 01 '17 at 10:59
  • Added the file, please take a look at it <3 – ConanCoder Jul 01 '17 at 11:15
  • @ConanCoder Sorry, but what you added now in your question is neither minimal nor compilable. –  Jul 01 '17 at 11:20
  • What do you mean ?? – ConanCoder Jul 01 '17 at 11:22
  • @ConanCoder Thhat I can't take it, drop it [here](http://coliru.stacked-crooked.com/a/35ec363ffaab7b55) and compile it. –  Jul 01 '17 at 11:26
  • Leave the rest, look only at the D'tor and removeRoom, where the line delete is found XD... what's wrong there :/ – ConanCoder Jul 01 '17 at 11:37
  • okay so here is the thing, I've fixed it, apparently the D'tor and the remove wasn't the wrong thing, IT WAS THE copy constructor and the opreator= it seems that when I want to copy or assign, I have to erase the memory then allocate it again for the new copy/assign object, that's what was causing the problem, how do I do that with std::set ?? – ConanCoder Jul 01 '17 at 11:49
  • @ConanCoder Sounds like you want to read [What is The Rule of Three?](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) –  Jul 01 '17 at 11:51
  • Yeah I know it, but I didn't implement it successfully :/ – ConanCoder Jul 01 '17 at 11:59
  • @catweazle-1964: You're correct, but I think OP's problem is wider than that, i.e. a questionable design choice (and also there's the assignment operator and the copy ctor). Pleasde have a look at my answer. – einpoklum Jul 01 '17 at 19:17
  • @einpoklum Well, your answer is probably correct, but OP already stated they _cannot use smart ponters_, so your answer might not help them out (but is valuable for further research of course). –  Jul 01 '17 at 19:23
  • @catweazle-1964: I didn't notice that restriction in the question, actually. Although, I did suggest `std::variant`s as a pointerless alternative. – einpoklum Jul 01 '17 at 19:25
0

If you just want to delete everything, then there's a much simpler solution. Delete everything first, then call clear(). You can turn the deletion part into a one-liner using std::for_each. Here is an example:

#include <algorithm>
// ...

std::for_each(begin(objects), end(objects), [](auto ptr) { delete ptr; });
objects.clear();

Note that if objects goes out of scope immediately afterwards, then clear() isn't even necessary.

If you want to remove a single element, then delete the pointer first and then erase it:

delete *iter; // decltype(iter) = std::set<A*>::iterator
objects.erase(iter);

Also consider using std::unique_ptr instead of raw pointers.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
  • tried yours too,and still crashes, it also stops on the `delete` line.. for some reason it can't delete. but I used `new` WTH .. Also I can only use raw pointers, because, it says so in the homework XD – ConanCoder Jul 01 '17 at 10:42
  • 1
    @ConanCoder Do your inherited classes have virtual destructor definitions? –  Jul 01 '17 at 10:50
  • Only the main one, I mean A .. it's the correct way, right ? he has `virtual ~A();` but B and C don't have any, they shouldn't, right ?? – ConanCoder Jul 01 '17 at 10:55
  • Added the file, please take a look at it <3, removeRoom and D'tor functions – ConanCoder Jul 01 '17 at 11:16
  • okay so here is the thing, I've fixed it, apparently the D'tor and the remove wasn't the wrong thing, IT WAS THE copy constructor and the opreator= it seems that when I want to copy or assign, I have to erase the memory then allocate it again for the new copy/assign object, that's what was causing the problem, how do I do that with std::set ?? – ConanCoder Jul 01 '17 at 11:50
  • @ConanCoder -- I was just about to state that the issue would more than likely not be the destructor. The destructor "not working" is an indication that you've trashed your program long before the destructor was called. – PaulMcKenzie Jul 01 '17 at 11:53
  • @ConanCoder Also, to copy and assign properly, we need to see your class definitions. You didn't post any of these defintions -- you did a whole lot of describing what they are, but nowhere did you actually post them. And no, posting implementations of member functions is not posting the definition / declaration of your class. – PaulMcKenzie Jul 01 '17 at 12:00
  • @ChristianHackl: I think your last sentence is the key here... and I've expanded on that in my answer. – einpoklum Jul 01 '17 at 19:22
0

I believe your question is an example of an XY problem: You want to empty your set of pointers, but you only want that because you want to manually destruct that set without leaking memory. And you only need to do that because automatic destruction won't take of that for you.

The real solution, as I see it, is to avoid the manual allocations with new. What would you do instead? There are probably 3 options:

  1. unique_ptr's
  2. shared_ptr's
  3. std::variant's

I'm guessing you don't really need to assign one company to another while keeping both; since your assignment operator and copy constructor semantics are wrong: you're making one company's rooms modifiable through the other company which is supposed to be const. You probably just perform move constructor:

Company::Company(Company&& company);

in which you would 'grab' the old company's room set, leaving it empty (and maybe a move-assignment operator). If that's the case - you only ever have hold one reference to a room, and unique_ptr<EscapeRoomWrapper> will do. You will not have to manually delete anything, and the default destructor for the set will work just fine. In fact, you might just be able to drop the custom move constructor altogether and just use the default.

If you do need multiple companies to refer to the same room, and do need those non-rvalue constructor and assignment operator - use shared_ptr<EscapeRoomWrapper>. Again, you won't need to delete anything, and the rooms will be deleted when the last reference to them is destructed. This will cost you a bit of overhead, but this isn't performance-critical code anyway so it doesn't matter.

Finally, if there is a limited variety of rooms, and they all inherit from the wrapper class - you might consider replacing the virtual inheritance with an std::variant of the different room classes, and not using pointer at all - just having a set of room.

For additional inspiration for my suggestions, consider reading about the rule of zero, e.g. in this blog post.

PS - Are you sure you need the set of rooms to be ordered? If not, use std::unordered_set instead of std::set.

einpoklum
  • 118,144
  • 57
  • 340
  • 684