0

I created a class book with some class properties such as name and author (nothing dynamically allocated), then I created a class library which contains dynamic book array (like book* set) and its size and some methods, so in library class I added to destructor a 'delete' operator to deallocate the memory of 'set', because set is a pointer, but in book class I only added some text like "Destructor was called" to understand whether all my books would be deleted after program execution or not.

And the main problem is that first I created 3 book objects and then I created a library object, where I added all my 3 books. And when I run my code, I see messages of library object destructor and only one book destructor except of 3, why does it happen? Does it really matter if destructor of non-dynamic objects is called or not? Because I know that it's a real problem if the destructor of dynamic objects is not called because it leads to memory leak, but what about my situation? Can someone please explain me how it actually works for local objects and dynamic in general. (I've just finished learning OOP so that's why I have some problems with understanding such things, also sorry for my poor English)

class book
{
private:
    string title;
    string author;
public:
    book() { title = ""; author = ""; }
    book(string n, string a) : title(n), author(a) {}
    string getTitle() { return title; }
    string getAuthor() { return author; }
    book& operator=(const book& other)
    {
        title = other.title;
        author = other.author;
        return *this;
    }
    ~book() { cout << "Destructor of book \"" << title << "\" " <<  "was called!\n"; }
};
class library
{
private:
    book* set;
    int size;
    int ptr;
public:
    library() 
    {
        size = 2;
        ptr = 0;
        set = new book[size];
    }
    void append(book &a)
    {
        if(ptr < size)
        {
            set[ptr] = a;
            ptr++;
        }
        else   // here I am just treating it as a simple dynamic array, so if I run out of space, I create a new dynamic array with n*2 size
        {
            book* copy = new book[size*2];
            for(int i = 0; i < size; i++)
                copy[i] = set[i];
            set = copy;
            set[ptr++] = a;
            size *= 2;
        }
    }
~library()
    {
        cout << "Destuctor of library was called.\n";
        delete set;
    }

};
int main()
{
    book one("Harry Potter", "J. K. Rowling");
    book two("The Little Prince", "Antoine de Saint-Exupery");
    book three("The Secret garden", "Frances Hodgson Bumett");
    library myLib;
    myLib.append(one);
    myLib.append(two);
    myLib.append(three);
    return 0;
}
comediann
  • 13
  • 2
  • 3
    Please show the code instead of describing it, ideally in the form of a [mcve]. – Quimby Aug 27 '23 at 13:53
  • Impossible to answer without a code, please provide [MRE](https://stackoverflow.com/help/minimal-reproducible-example). Aside, do not use `set`, use `set` (or `set>` if you really have to for whatever reason, e.g. because of inheritance), and do not confuse [`delete[]` with `delete`](https://stackoverflow.com/questions/2425728/delete-vs-delete-operators-in-c) – yeputons Aug 27 '23 at 13:53
  • 2
    If you have a choice about whether to use pointers or not, you should avoid using them. In most cases they are not necessary, and it's difficult to use them correctly. – Jeremy Friesner Aug 27 '23 at 13:55
  • The code shown never destroys dynamically allocated `book` objects. They are leaked. Only the three objects declared on the stack in `main` are destroyed; thus, the program [prints three lines](https://godbolt.org/z/rrhWvbP9x) – Igor Tandetnik Aug 27 '23 at 14:23
  • 2
    Despite how some tutorials make it look, `new`, `delete`, and destructors (and custom copy constructors and assignment operators) are advanced features that most programmers should almost never use. Prefer `vector`, `unique_ptr`, etc. – HolyBlackCat Aug 27 '23 at 14:25
  • You have applied the "Rule of Three" to class `book`, but it is not needed there. The compiler-supplied, default versions of the destructor, copy constuctor, and assignment operator all do just the right thing. Class `library` is the place to use the "Rule of Three." That class is basically a duplicate of `std::vector`, except it is incomplete (and sometimes wrong). If you want to learn the right way to create a "vector" class, watch this video by Arthur O'Dwyer: [Back to Basics: RAII and the Rule of Zero](https://www.youtube.com/watch?v=7Qgd9B1KuMQ). It is from a talk he gave at CppCon 2019. – tbxfreeware Aug 27 '23 at 14:55
  • Here is some code based on the talk by Arthur O'Dwyer: [Copy semantics and vectors](https://stackoverflow.com/a/76810137/22193627). It may be fruitful to read the complete question and answer there. – tbxfreeware Aug 27 '23 at 15:04
  • 1
    You wrote `delete` where you should have written `delete []`, triggering undefined behaviour. – molbdnilo Aug 27 '23 at 15:23
  • @molbdnilo oh yeah, I've noticed that, thank u! – comediann Aug 27 '23 at 15:27

1 Answers1

1
Your library class is a std::vector in disguise.

If you change the name of member function append to push_back, you can really see the similarity.

Question 1

when I run my code, I see messages of library object destructor and only one book destructor except of 3, why does it happen?

The problem here was identified several times in the comments. You forget to use the array version of the delete operator. Here is the code you have in your question:

    ~library()
    {
        cout << "Destuctor of library was called.\n";
        delete set;
    }

And this is the correction:

    ~library()
    {
        cout << "Destuctor of library was called.\n";
        delete[] set;
    }

With that simple change, everything snaps into place.

Here is the output:

Destuctor of library was called.
Destructor of book "" was called!
Destructor of book "The Secret garden" was called!
Destructor of book "The Little Prince" was called!
Destructor of book "Harry Potter" was called!
Destructor of book "The Secret garden" was called!
Destructor of book "The Little Prince" was called!
Destructor of book "Harry Potter" was called!

At first, it may seem odd that the dtor for book "" was called. That is explained, however, by the fact that the default ctor for book is invoked by operator new. It was called twice by the default ctor for library, and four times more when you called operator new from function append.

After appending three books, the final slot in the library remained unused. That is the one that you see getting destructed in the output.

Elements of the array set are destoyed in the reverse order of their construction. That explains the sequence of destructor calls shown in the output above. The first four show myLib getting destroyed. The final three show the local variables in function main being destroyed.

Oh, no! A memory leak...

In function append, you forgot to delete the old array. Here is the fix:

    void append(book& a)
    {
        if (ptr < size)
        {
            set[ptr] = a;
            ptr++;
        }
        else   // here I am just treating it as a simple dynamic array, so if I run out of space, I create a new dynamic array with n*2 size
        {
            book* copy = new book[size * 2];
            for (int i = 0; i < size; i++)
                copy[i] = set[i];
            std::swap(set, copy);  // Swap the pointers
            delete[] copy;         // Then, delete the copy.
            set[ptr++] = a;
            size *= 2;
        }
    }

This gives you two more calls to the dtor for book:

Destructor of book "The Little Prince" was called!
Destructor of book "Harry Potter" was called!
Destuctor of library was called.
Destructor of book "" was called!
Destructor of book "The Secret garden" was called!
Destructor of book "The Little Prince" was called!
Destructor of book "Harry Potter" was called!
Destructor of book "The Secret garden" was called!
Destructor of book "The Little Prince" was called!
Destructor of book "Harry Potter" was called!
Question 2

Does it really matter if destructor of non-dynamic objects is called or not?

The question is moot. An object's dtor is going to be called whenever its lifetime ends. Even if an object is allocated on the stack, it can be important to call the destructor. Your book class is an example. Although it does not contain any dynamically allocated data that was created by you, it does contain a pair of std::string objects, and those guys probably do store data on the heap.

What about production code?

Your library program may be a good exercise for learning about dynamically allocated data. Everyone needs to do something like that sooner or later. In production code, however, you are better off using one of the containers from the Standard Library.

class library
{
private:
    std::vector<book> books;
public:
    library() {
        books.reserve(2);
    }
    void append(book& a) {
        books.push_back(a);
    }
    // ...
};

You might even eliminate class library completely:

int main()
{
    book one("Harry Potter", "J. K. Rowling");
    book two("The Little Prince", "Antoine de Saint-Exupery");
    book three("The Secret garden", "Frances Hodgson Bumett");
    std::vector<book> myLib;
    myLib.push_back(one);
    myLib.push_back(two);
    myLib.push_back(three);
    return 0;
}

I hope this answer has been of help to you.

tbxfreeware
  • 547
  • 1
  • 9