1

When exactly is destructor being called? Initially, I thought that destructor were supposed to be called when we use the:

delete [] str;

Then, when I came accross the "move assignment" topic, I notice that the destructor is called directly after I NULL my rhs.str in my move assignment method.

main.cpp

int main() {

Mystring a{"Hello"};                // Overloaded constructor
a = Mystring{"Hola"};              // Overloaded constructor then move assignment
// cout << a.get_str() << endl;
a = "Bonjour";                         // Overloaded constructor then move assignment
return 0;
}

Mystring.h

#ifndef _MYSTRING_H_
#define _MYSTRING_H_

class Mystring
{
private:
    char *str;      // pointer to a char[] that holds a C-style string
public:
    Mystring();                                            // No-args constructor
    Mystring(const char *s);                                     // Overloaded constructor
    Mystring(const Mystring &source);                    // Copy constructor
    Mystring(Mystring &&source);                         // Move constructor
    ~Mystring();                                                     // Destructor

    Mystring &operator=(const Mystring &rhs); // Copy assignment
    Mystring &operator=(Mystring &&rhs);        // Move assignment

    void display() const;

    int get_length() const;                                       // getters
    const char *get_str() const;
};

#endif // _MYSTRING_H_

Mystring.cpp

// No-args constructor
Mystring::Mystring() 
    : str{nullptr} {
    str = new char[1];
    *str = '\0';
}

// Overloaded constructor
Mystring::Mystring(const char *s) 
    : str {nullptr} {
    if (s==nullptr) {
    str = new char[1];
    *str = '\0';
    } else {
        str = new char[strlen(s)+1];
        strcpy(str, s);
    }
}

// Copy constructor
Mystring::Mystring(const Mystring &source) 
    : str{nullptr} {
    str = new char[strlen(source.str)+ 1];
    strcpy(str, source.str);
    std::cout << "Copy constructor used" << std::endl;

}

// Move constructor
Mystring::Mystring(Mystring &&source) 
     : str(source.str) 
    {
         source.str = nullptr;
         std::cout << "Move constructor used" << std::endl;
    }

 // Destructor
Mystring::~Mystring() 
{
    if (str == nullptr) {
    std::cout << "Calling destructor for Mystring : nullptr" << std::endl;
    } else {
    std::cout << "Calling destructor for Mystring : " << str << std::endl;
}
    delete [] str;
}

 // Copy assignment
Mystring &Mystring::operator=(const Mystring &rhs) {
    std::cout << "Using copy assignment" << std::endl;

    if (this == &rhs) 
        return *this;
    delete [] str;
    str = new char[strlen(rhs.str) + 1];
    strcpy(str, rhs.str);
    return *this;
}

//Move assignment
Mystring &Mystring::operator=(Mystring &&rhs) {
    std::cout << "Using move assignment" << std::endl;
    if (this == &rhs) 
        return *this;
    delete [] this->str;
    this->str = rhs.str;
    rhs.str = nullptr;
    return *this;

}

The output is as follow:

Using move assignment
Calling destructor for Mystring : nullptr
Using move assignment
Calling destructor for Mystring : nullptr
Calling destructor for Mystring : Bonjour

I have tried debugging but I just have no idea why it went into the Destructor method when I did not explicitly called the

delete [] str  // or maybe
delete [] rhs.str 

As you can see in the output, my r-value is deleted after every move assignment. I thought that every object should at least be deleted before the return 0;

  • 1
    All temporary objects get destroyed at the end of the full expression they are in. https://stackoverflow.com/questions/2506793/c-life-span-of-temporary-arguments – NathanOliver Aug 11 '21 at 12:07
  • Thank you for the links. I was not aware of it having a temporary lifetime. If I were to summarise it simply from both the links I received, that'd be: *The destructor for that sort of temporaries is called at the end of the full-expression.* – Khalid Owl Walid Aug 11 '21 at 13:50

2 Answers2

6

When exactly is destructor being called?

Destructor is called when an object is destroyed of course. When that happens depends on the storage duration of the object.

I thought that destructor were supposed to be called when we use the:

delete [] str;

That's one case where an array of objects with dynamic storage duration is destroyed. That said, in the case of str those objects are of type char which have trivial destructors and thus there's nothing to "call" in practice.

int main() {
Mystring a{"Hello"}; 

The variable a has automatic storage duration. That object is destroyed at the end of the scope where it was declared (which is at the end of the function main). This is the destructor that you see last in the output.

 a = Mystring{"Hola"};

There's a temporary object created here, used as the right hand operand of the assignment operator. The temporary object is destoyed at the end of the full-expression i.e. after the assignment. This is the destructor that you see first in the output.

a = "Bonjour";

This is same as the previously examined snippet, except the temporary object is less obvious because you're using an implicit converting constructor of the class. The destructor of this object is the one that you see between the first and the last in the output.


P.S.

#define _MYSTRING_H_

That name is reserved to the language implementation. By defining it, your program will have undefined behaviour. You should use another header guard that isn't reserved.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

Move semantics do not change, in any way, the fundamental principles and rules of how and when objects get created and destroyed in C++. An object in automatic scope gets destroyed at the end of its automatic scope. An object in dynamic scope gets destroyed when it gets deleted. The End.

After a move constructor and/or move assignment operator gets defined for a class, its objects will continue to get constructed and destroyed at the same time, and place, where they were constructed and destroyed before move semantics were defined. This is not changed by move semantics.

All move semantics provide are an optimization path, to avoid unneeded work when copying objects effects a logical move.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148