-4

The following piece of code has a user defined data type called String.Objects of this class store a char pointer str(short hand for string) and length.

#include<iostream>
#include<cstring>
using namespace std;
class String
{
    char* str;
    int length;
public:
    String(){}                     //default constructor 
    String(const char* s)
    {
        length=strlen(s);
        str=new char[length+1];   
        strcpy(str,s);
    }
    void add(String a,String b)   //function to concatenate strings
    {
        length=a.length+b.length;
        str=new char[length+1];
        strcpy(str,a.str);
        strcat(str,b.str);
    }
    void display()
    {
        cout<<str<<endl;
    }
    ~String()                                //destructor 
    {
        delete str;
        cout<<"Destructor invoked!";
    }
};
int main()
{
    String s1;
    String s2("Well done!");
    String s3("Boy");
    s1.add(s2,s3);
    s1.display();
    s2.display();
    s3.display();
}

Output:

Destructor invoked!Destructor invoked!Well done!boy
X!!;
<!!;
Destructor invoked!Destructor invoked!Destructor invoked!
  • It appears as though the destructor is invoked even before the display functions get invoked.Why is this?

If the destructor function were not defined, I get the following output(as expected):

Well done!boy
Well done!
boy 
  • Why is this unexpected output upon defining a destructor?
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
user7952
  • 27
  • 6

2 Answers2

1

You need copy constructor for this: void add(String a,String b); It's better to pass strings by reference:

void add(const String &a, const String &b);

Also in destructor you need to free the memory with delete[].

delete[] str;

Now it will work.

Lassie
  • 853
  • 8
  • 18
1

This default constructor

String(){} 

doesn't initialize anything, because the two data members are of fundamental types and a fundamental type doesn't provide automatic initialization.

Any use of the resulting instance will therefore use indeterminate values (of the data members), and will therefore have Undefined Behavior.

Fix: initialize the data members.


You also need to take charge of copying and moving, e.g. to avoid double deletes – which is also Undefined Behavior.

The example code copies String instances in the call

s1.add(s2,s3);

… because add takes the arguments by value.

Fix: define at least a copy constructor.


In the destructor the deallocation

delete str;

… does not match the allocation of str, which used new[].

It can work in practice, but it's Undefined Behavior and therefore can cause all sorts of issues.

Fix: use delete[] for something created with new[].


After fixing the above the code will still leak memory. A simple fix could be to define a concatenating constructor, let add use that to create a new String instance, and then swap the states of that instance and the current instance. This approach has the advantage of automatically (well, almost automatically) being exception safe.


General advice:

  • Instead of <cstring>, just use <string.h>. One advantage is that like the C header of the same name, it guarantees to place the names in the global namespace. So if you use unqualified names like strcpy it will work portably, not just by happenchance for the compiler at hand.

  • Instead of passing potentially large objects by value, as in void add(String a,String b), pass them by reference to const, as in void add(String const& a,String const& b), to avoid needless copying.

  • Preferentially don't do i/o, like the void display() member function, in a class that isn't concerned with i/o. For example, the display() function won't work in a graphical user interface.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331