Hi...

I am trying to write a string class...this part is supposed to concatenate two strings, but I am having trouble with constructors/destructors.:rolleyes:

Please explain the exact sequence of calls and the correct way to code.:?:

#include <iostream>
#include <cstring>
#include <conio.h>
using namespace std;

class STRING
{
      char *str;
      int len;
      
 public:

       STRING( ) {   cout<<"*** Default Constructor called. ***"<<endl;   str = NULL, len = 0; }
       STRING( const char *s );
       STRING( const STRING& );
       ~STRING( );

       friend ostream& operator<<( ostream &os, STRING  s );
       STRING operator+( STRING &s );
};


STRING::STRING( const char *s )
{
     cout<<"*** String constructor called. ***\n";
     len = strlen( s );
     str = new char[len+1];
     strcpy( str, s );
}

STRING::STRING( const STRING& s )
{
     cout<<"*** Copy constructor called. ***\n";
     len = s.len;
     str = new char[len+1];
     strcpy( str, s.str );
}

STRING::~STRING( )
{
     cout<<"*** Destuctor called. ***"<<endl;
     delete[] str;
     len = 0;
}

ostream& operator<<( ostream &os, STRING s )
{
 	if( s.len == 0 )
 	    cout<<"(Empty)\n";
	else
    	os<<s.str<<endl;
    return os;
}

STRING STRING::operator+( STRING &s )
{
    STRING res;
    
    res.len = len + s.len;
    res.str = new char[res.len+1];
    
    strcpy( res.str, str );
    strcat( res.str, s.str );
    
    return res;
}

int main( )
{
    STRING s1("String1");
    STRING s2("String2");
    STRING s3;
    
    s3 = s1+s2;
    
    cout<<s1<<s2<<s3;
    
    getch( );
    return 0;
}

Here's what I think happens.

STRING s1("String1");  //string constructor called
STRING s2("String2");  //string constructor called
STRING s3;                 //default constructor called
    
    s3 = s1+s2;           //default constructor called within the + operator
                                //copy constructor called by compiler to create temporary object from res returned from + operator
                                //destructor called to remove res
                                //assignment operator called using default assignment constructor provided by compiler
                                //destructor called to remove temporary object
    
    cout<<s1<<s2<<s3;
    
    getch( );
    return 0;    //destructor called to destroy s1, s2, and s3.

If that is how it happens, how come I dont get correct answer for s3 ( the result is shown blank, it must be String1String2 )

Comments
Figure out yourself

Hi I just made one now, this appears to work although it's more of a trial and error thing.

I've highlighted the part that may be suspect in blue. Someone who knows what they're doing can tell you if my coding is logical and safe.

#include <iostream>
#include <iomanip>
#include <cstring>


class String 
{
private:
       char *mem;
       int size;
public:
       String ( const char *p );
       ~String();

  
  friend std::ostream& operator<< ( std::ostream& os, const String& s );
  String operator+(String); //Overloaded + operator
};

String::String ( const char *p )
{
  size = strlen ( p );
  mem = new char[size + 1];
  strcpy ( mem, p );
}

String::~String() 
{ 
    delete [] mem; 
}



std::ostream& operator<< ( std::ostream& os, const String& s )
{
  return os << s.mem;
}

String String::operator+(String c) 
{ 
    char *crap;
       crap = new char[size + c.size+1];
    
    strcpy(crap,mem);
     
    strcat(crap,c.mem);
    String temp = crap;
    
    return temp;
}

int main()
{
  String a = "My cool string";
  String b = " is so crappy";
  String c = " ya know";
  
  String d = a  + b + c;
  
  std::cout << d << '\n';
  
  
  std::cin.get();
  return 0;
  
}

I dont think the code will work if you write it as: ( in main )

String a = "My cool string";
  String b = " is so crappy";
  String c = " ya know";

String d; 

d = a + b + c ;

This makes all the difference.

I dont think the code will work if you write it as: ( in main )

String a = "My cool string";
  String b = " is so crappy";
  String c = " ya know";

String d; 

d = a + b + c ;

This makes all the difference.

Couldn't you just overload the equals operator then? I don't know, it's got to be something simple right?

Run time debugging is cumbersome. Here's how I do it; lots of output statements with pauses to see that status is as I want it to be. I suspect everybody does it differently.

#include <iostream>
#include <cstring>
#include <conio.h>
using namespace std;

class STRING
{
      char *str;
      int len;
      
 public:

       STRING( ) {   cout<<"*** Default Constructor called. ***"<<endl;   str = NULL, len = 0; }
       STRING( const char *s );
       STRING( const STRING& );
       ~STRING( );

       friend ostream& operator<<( ostream &os, const STRING &  s );  //slight change to syntax here
       STRING operator+( STRING &s );
};


STRING::STRING( const char *s )
{
     cout<<"*** String constructor called. ***\n";
     len = strlen( s );
     str = new char[len+1];
     strcpy( str, s );
}

STRING::STRING( const STRING& s )
{
     cout<<"*** Copy constructor called. ***\n";
     len = s.len;
     str = new char[len+1];
     strcpy( str, s.str );
}

STRING::~STRING( )
{
     cout<<"*** Destuctor called. ***"<<endl;
     delete[] str;
     len = 0;
}

ostream& operator<<( ostream &os, const STRING & s ) //slight change here
{
 	if( s.len == 0 )
 	    os<<"(Empty)";  //slight change here
	else
    	   os<<s.str;           //slight change here
    return os;
}

STRING STRING::operator+( STRING &s )
{
    STRING res;
    char ch;

    cout << "concatenation opereator called" << endl;

    res.len = len + s.len;
    res.str = new char[res.len+1];
    
    strcpy( res.str, str );

    cout << "res.str now is" << res.str << endl;
    cin >> ch;

    strcat( res.str, s.str );
    
    cout << "res.str now is " << res.str << endl;
    cin >> ch;

    cout << "end concatenation operator" << endl;

    return res;
}

int main( )
{
    STRING s1("String1");
    STRING s2("String2");
    STRING s3;
    
    //confirm expected values
    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;
    
    s3 = s1+s2;
    
    //determine if assignment operator worked.
    cout << s3;
    cout << endl;
    
    cout << s1 << endl << s2 << endl << s3;  //slight change in syntax

    getch( );
    return 0;
}

Couldn't you just overload the equals operator then? I don't know, it's got to be something simple right?

Overloading the = operator is same as writing a copy constructor, the same thing will be called when

s3 = s1 + s2;

is executed.

Overloading the = operator is same as writing a copy constructor

The implementation of copy constructors and assignment operators is frequently similar, but they are not the same.

Overloading the = operator is same as writing a copy constructor, the same thing will be called when

s3 = s1 + s2;

is executed.

hmm, yeah I was just taking a stab at what might work. Probably overloading the equals operator won't make much difference if any?

Question: Has lerner's program helped in bringing you closer to a solution that you want?

To change :

string d = a + b + c

to:-

string d;
d = a + b + c

Seems such a trivial exercise however, I can't find any examples on the net. Surely someone else knows how to do it? :sad:

Lerner's solution gave some runtime errors. Here is a modified version of the OPs class. I passed most of the parameters by reference so that a local copy of the parameter (thereby cluttering the output) is not called everytime a function is called.

#include <iostream>
#include <cstring>
using namespace std;

class STRING
{
      char *str;
      int len;

 public:

       STRING( ) {   cout<<"*** Default Constructor called. ***"<<endl;   str = NULL, len = 0; }
       STRING( const char *s );
       STRING( const STRING& );
       ~STRING( );

       friend ostream& operator<<( ostream &os, const STRING&  s );
       STRING& operator+( const STRING &s );
};


STRING::STRING( const char *s )
{
     cout<<"*** String constructor called. ***\n";
     len = strlen( s );
     str = new char[len+1];
     strcpy( str, s );
}

STRING::STRING( const STRING& s )
{
     cout<<"*** Copy constructor called. ***\n";
     len = s.len;
     str = new char[len+1];
     strcpy( str, s.str );
}

STRING::~STRING( )
{
     cout<<"*** Destuctor called. ***"<<endl;
     delete[] str;
     len = 0;
}

ostream& operator<<( ostream &os, const STRING& s )
{
     if( s.len == 0 )
         cout<<"(Empty)\n";
    else
        os<<s.str<<endl;
    return os;
}

STRING& STRING::operator+( const STRING &s )
{
    len = len + s.len;
    char* temp = new char[len+1];
    strcpy( temp, str );
    strcat( temp, s.str );
    if (str )
        delete []str;
    str = temp;
    return (*this);
}

int main( )
{
    STRING s1("String1");
    cout << "Created s1\n" << s1;
    STRING s2(s1);
    cout << "Created s2\n" << s2;
    STRING s3 ;
    cout << "Created s3\n" << s3;
    s3 = s1+s2;
    cout << "Added s1 and s2\n" << s3;
    return 0;
}

Yep that looks like the stuff.

What does this part do STRING( const STRING& ); and this:-

//STRING::STRING( const STRING& s )
{
    cout<<"*** Copy constructor called. ***\n";
    len = s.len;
     str = new char[len+1];
    strcpy( str, s.str );
}

I commented it out and it didn't seem to make any difference?

One other thing wolfie. It doesn't seem to work for this case.

int main( )
{
    STRING s1;
    s1 = "hello";
    cout << s1;
    cin.get();
    return 0;
}

Any ideas? Would you have to overload the equals operator or something?

It also fails for other stuff like:

STRING b("there");
STRING a = "hello" + b;

That is a copy constructor. The standard says that if a copy constructor is not explicitly declared, a copy constructor is implicitly declared. So it is not necessary. Similar for the copy assignment constructor.

One other thing wolfie. It doesn't seem to work for this case.

int main( )
{
    STRING s1;
    s1 = "hello";
    cout << s1;
    cin.get();
    return 0;
}

Any ideas? Would you have to overload the equals operator or something?

Yes you will have to overload the equals operator. If you have not declared one, the compiler generates one implicitly in the form of

String& operator=(const String& );

. So even though

s = someotherstringobject;

will work,

s = "somecharacterstring"

will not work.

It also fails for other stuff like:

STRING b("there");
STRING a = "hello" + b;

Like I said these are not defined. if you want to use the assignment operator like this you will have to declare it. I am not sure if you will be able to get "hello" + b to work. You should be able to get b + "hello" to work but not the other way around.

So how would you ammend your code to allow for such cases:-

STRING b("there");
STRING a = "hello" + b;
int main( )
{
    STRING s1;
    s1 = "hello";
    cout << s1;
    cin.get();
    return 0;
}

I am not sure if you will be able to get "hello" + b to work. You should be able to get b + "hello" to work but not the other way around.

How the hell does the std::string implementation do that then? I mean allow for "hello" + b

So how would you ammend your code to allow for such cases:-

STRING b("there");
STRING a = "hello" + b;
int main( )
{
    STRING s1;
    s1 = "hello";
    cout << s1;
    cin.get();
    return 0;
}
STRING& STRING::operator+( const STRING &s )
{
    len = len + s.len;
    char* temp = new char[len+1];
    strcpy( temp, str );
    strcat( temp, s.str );
    if (str )
        delete []str;
    str = temp;
    return (*this);
}

STRING& STRING::operator+( const char* s )
{
    len = len + strlen(s);
    char* temp = new char[len+1];
    strcpy( temp, str );
    strcat( temp, s );
    if (str )
        delete []str;
    str = temp;
    return (*this);
}

STRING& STRING::operator=( const STRING &s )
{
    len = s.len;
    char* temp = new char[len+1];
    strcpy( temp, s.str );
    if (str )
        delete []str;
    str = temp;
    return (*this);
}

STRING& STRING::operator=( const char* s )
{
    len = strlen(s);
    char* temp = new char[len+1];
    strcpy( temp, s );
    if (str )
        delete []str;
    str = temp;
    return (*this);
}

The above code should allow you to do s + "hello" and the such. But as I said not the "hello" + s; part. I will have to lookup further.

The above code should allow you to do s + "hello" and the such. But as I said not the "hello" + s; part. I will have to lookup further.

hmm, yes I never realised how much thought goes into designing these classes.

Or how complicated it can become:- If you're telling me the right stuff of course;).

To think we've only really touched the surface the string class.

ha ha.

I guess you can get the char* + string behaviour if you implement the following friend function.

friend STRING operator+( const char* s, const STRING& s1 );
STRING operator+( const char* s, const STRING& s1 )
{
    char*  tempstr;
    int len = s1.len + strlen(s);
    tempstr = new char[len+1];
    strcpy( tempstr,  s);
    strcat( tempstr, s1.str );
    return STRING(tempstr);
}

I just don't get it...its clearly mentioned in many books that if a copy constructor is explicitly defined it'll be called. In this program we have done that...and it creates a new object...then why this problem??

One more thing, the program I've originally written works if we make this modification:

STRING s3 = s1 + s2;  // STRING s3; s3 = s1 + s2

Any ideas, any body...??:confused:

That is a copy constructor. The standard says that if a copy constructor is not explicitly declared, a copy constructor is implicitly declared. So it is not necessary. Similar for the copy assignment constructor.

A copy constructor is really needed in this case because we are allocating space dynamically. If you let the default copy constructor do the job, it'll mess up the whole thing when you pass parameters by value.
Here's why :

string a("Hello");
string b = a;

The default constructor provided does a bit-by-bit copy. So both a and b will point to the same location. If one of them is destroyed ( say by a destructor when returning from the function ), the other will point to an invalid location.

I just don't get it...its clearly mentioned in many books that if a copy constructor is explicitly defined it'll be called. In this program we have done that...and it creates a new object...then why this problem??

One more thing, the program I've originally written works if we make this modification:

STRING s3 = s1 + s2;  // STRING s3; s3 = s1 + s2

Any ideas, any body...??:confused:

The problem here is you are confusing the copy constructor and assignment.

STRING s3;

You are creating s3 in this line. This is default construction. So the default constructor will be called.

s3 = s1 + s2;

You are assigning the result of s1 + s2 to s3. There is no objects created here. There is only assignment. So no constructors will be called here.

STRING s3 = s1 + s2;

In this line, you are using the copy construction. The result will be a copy of (s1 + s2). So here the copy constructor will be called.

Does this agree with your observations?

A copy constructor is really needed in this case because we are allocating space dynamically. If you let the default copy constructor do the job, it'll mess up the whole thing when you pass parameters by value.
Here's why :

string a("Hello");
string b = a;

The default constructor provided does a bit-by-bit copy. So both a and b will point to the same location. If one of them is destroyed ( say by a destructor when returning from the function ), the other will point to an invalid location.

Yes. That is true. What I meant was that it is not necessary to define it if the default behaviour is what you expect. But yes, I get the run time error when the explicit copy constructor is commented out. This fact is clearly explained in stroustrup - The C++ Programming Langauge section 10.4.4.1 . I guess I should have explained a bitmore.

The problem here is you are confusing the copy constructor and assignment.

STRING s3;

You are creating s3 in this line. This is default construction. So the default constructor will be called.

s3 = s1 + s2;

You are assigning the result of s1 + s2 to s3. There is no objects created here. There is only assignment. So no constructors will be called here.

STRING s3 = s1 + s2;

In this line, you are using the copy construction. The result will be a copy of (s1 + s2). So here the copy constructor will be called.

Does this agree with your observations?

With reference to my original program here's the output with for the two cases

1.

string s3;
s3 = s1 + s2;

Output:

*** String constructor called. *** // For s1
*** String constructor called. *** // For s2

*** Default Constructor called. *** // For s3

*** Default Constructor called. *** // STRING res in operator +
*** Destuctor called. *** // Destroying res

*** Copy constructor called. *** // Maybe for executing cout<<s1
*** Copy constructor called. *** // <<s2<<s3
*** Copy constructor called. ***

String1
String2

*** Destuctor called. *** // s1
*** Destuctor called. *** // s2
*** Destuctor called. *** // s3
2.

string s3 = s1 + s2;

Output

*** String constructor called. ***
*** String constructor called. ***
*** Default Constructor called. *** // Same as above till here

*** Copy constructor called. *** // Again, for cout<<s1<<s2<<s3
*** Copy constructor called. ***
*** Copy constructor called. ***

String1
String2
String1String2 // Hey, correct output:lol:

*** Destuctor called. *** // Obvious
*** Destuctor called. ***
*** Destuctor called. ***
Am I right???
If I am, the copy constructor is never involved in s3=s1+s2 in any case.

One more thing Wolfie...

I really dont get difference between assignment and copy constrcutor, try to explain:confused:

Since there is no need to see the contents I used the following main function.

int main( )
{
    STRING s1("String1");
    STRING s2("String2");
    cout << "Creating s3 using copy constructor\n";
    STRING s3 = s1+s2;
    cout << "Created s3 using copy constructor\n";
    return 0;
}

This is the output

*** String constructor called. ***
*** String constructor called. ***
Creating s3 using copy constructor
*** Default Constructor called. *** // This one for res
*** Copy constructor called. *** // Here s3 is initialized with (s1 + s2 )
*** Destuctor called. *** // This one for res
Created s3 using copy constructor
*** Destuctor called. ***
*** Destuctor called. ***
*** Destuctor called. ***

For the seperate one, the main function was this.

int main( )
{
    STRING s1("String1");
    STRING s2("String2");
    cout << "Creating s3 using default constructor\n";
    STRING s3;
    cout << "Created s3 using default constructor\n";
    cout << "Assigning s1 + s2 to  s3\n";
    s3 = s1+s2;
    cout << "Assigned s1 + s2 to  s3\n";
    return 0;
}

The output was

*** String constructor called. ***
*** String constructor called. ***
Creating s3 using default constructor
*** Default Constructor called. ***
Created s3 using default constructor
Assigning s1 + s2 to s3
*** Default Constructor called. *** // For res
*** Copy constructor called. *** // Most probable for a temporary variable created by the compiler. Not sure.
*** Destuctor called. ***
*** Destuctor called. *** // Get a run time error
Assigned s1 + s2 to s3
*** Destuctor called. ***// Get a run time error
*** Destuctor called. ***

As you can see there are errors in this code. Correct them and then try to understand again. Or you can try my version of STRING to see what are the constructors called for the above main functions. It is much clearer IMHO.

*** String constructor called. ***
*** String constructor called. ***
Creating s3 using copy constructor
*** Copy constructor called. ***
Created s3 using copy constructor
*** Destuctor called. ***
*** Destuctor called. ***
*** Destuctor called. ***

*** String constructor called. ***
*** String constructor called. ***
Creating s3 using default constructor
*** Default Constructor called. ***
Created s3 using default constructor
Assigning s1 + s2 to s3
Assigned s1 + s2 to s3
*** Destuctor called. ***
*** Destuctor called. ***
*** Destuctor called. ***

As for the difference between assignment and copy constructor,
assignment can be done at any moment in the life time of the object.
the copy constructor is called only ( but not always) when you create the object.

STRING s1(); //uses default constructor
STRING s2(s1); //uses a copy constructor
STIRNG s3("String3"); //uses a non default, nont copy constructor

s3 = s1 //this is assignment
STRING s4 = s1 + s2; //this is initialization of s4 using result of s1 + s2 and uses the copy constructor, not the assignment operator. Even though the = is there, it's not the assignment operator, but I've never seen it given a name when used under these circumstances. You just have to know that this is initialization and not assignment.

Copy constructors create new objects using the information from an existing object. Additional memory must be allocated for the new object.

Assignment operators use the information from an existing object to change the information in an existing object. No additional memory needs to be allocated.

In order to do this:

const char * Cstring = "C style string";
s4 = Cstring + s1;

or this:

s4 = "C style string" + s1;


I think you will need to overload the + operator to use two STRING objects and make it a friend method of the STRING class and be sure the STRING class has the constructor that changes char * to a STRING. In my reference that constructor looks like this:

STRING(const char * const);

and the friend method looks like:

friend STRING operator+(const STRING &, const STRING &);

Then when the compiler sees this:

s4 = cString + s1;

it can't find a + operator that takes a C style string and a STRING as parameters, but it can find a + that takes two STRINGs and it can find a constructor that converts char * into a STRING so it converts cString into a STRING and calls the + operator taking two STRINGs to get the concatenation done and then uses the assignment operator to assign the value of the return value of + to s4 (I admit, whether it copies the return value of + into a new temporary object and assigns the temporary object to s4 or whether it assigns the return value directly to s4 without going through a temporary object, I'm not absolutely sure).

I think the reason Wolfpacks code works with the default assignment operator provided by the compiler whereas vicky_devs does not when this:

s3 = s1 + s2;

is tried has more to do with the default version of the assignment operator rather than the code of the overloaded + operator. If we assume that the default version of the assignment operator does a shallow copy of the left hand side (lhs) it will assign the address of lhs.str to str. In Wolfpacks version of +, this is okay because the adrress stored in the this pointer of s1 is still valid when assignment is completed, but the address of res in vichy_devs code will be deleted after the return of the + operator is done and therefore s3.str will not contain a valid address---it is a stranded pointer. Therefore trying to delete it will fail since there isn't an address in it, and trying to display s3 with << will fail because s3.str is neither NULL nor "real", it is empty.

If vichy_dev were to overload the assignment operator to do a deep copy, as Wolfpack did in one of his posts:

STRING& STRING::operator=( const STRING &s )
{
    len = s.len;
    char* temp = new char[len+1];
    strcpy( temp, s.str );
    if (str )
        delete []str;
    str = temp;
    return (*this);
}

rather than rely on the default assignment operator supplied by the compiler, then I think vichy_devs code for the + operator will work, too, because now the information pointed to by res.str is stored at the address in s3.str; that is, the address of res.str isn't given to s3.str. Therefore, it doesn't matter that res is now deleted because the information in res is now stored at a different address. Vichy_devs copy construcor already does a deep copy to it doesn't matter whether there is a copy constructor involved in:

s3 = s1 + s2;

or not, it matters whether the assignment operator does a shallow copy or a deep copy.

I don't have a compiler handy to test this theory, but if it's true, then the moral of the story would be to always overload the = operator if you overload the copy constructor.

This question has already been answered. Start a new discussion instead.