0

I have a somehow basic question regarding the conversion constructors and assignment operators. I can't find a similar question but maybe I am searching wrongly. Anyway.. I had made a class like this

class String
{
private:
//    enum { SZ = 80 };
    static const int SZ = 80;
    char str[SZ]; //array
public:
    String() //constructor, no args
    {
        cout << "Default constructor called, p_str = " << (void*)str << endl;
        strcpy(str, "");
    }
    String( char s[] ) //constructor, one arg
    {
        cout << "Copy constructor called, p_str = " << (void*)str << endl;
        strcpy(str, s);
    }
    void display() //display string
    {
        cout << str << endl;
//        cout << "str ptr = " << (void*)str << endl;
    }
    void concat(String s2) //add arg string to
    { //this string
        if( strlen(str)+strlen(s2.str) < SZ )
            strcat(str, s2.str);
        else
            cout << "\nString too long";
    }
    void SetString(char* strToSet)
    {
        strcpy(str, strToSet);
    }
//    void operator =(const char* strCpy)
//    {
//        cout << "Copy assignemnt called..." << endl;
//        strcpy(str, strCpy);
//    }
    ~String()
    {
        cout << "Destructor called..." << endl;
    }
    void* GetStrPtr()
    {
        return (void*)str;
    }
};

and in the main:

    String myStr1 = "Hello Hello";

    void* old_str_ptr = myStr1.GetStrPtr();
    cout << "old_str_ptr = " <<old_str_ptr << endl;

    myStr1 = "hello World!!";
    cout << "old_str_ptr = " <<old_str_ptr << endl;
    void* new_str_ptr = myStr1.GetStrPtr();
    cout << "new_str_ptr = " <<new_str_ptr << endl;
    myStr1.display();
    cout << (char*)old_str_ptr << endl;
    cout << (char*)new_str_ptr << endl;

This is the output I got:

Copy constructor called, p_str = 0x62fdd8
old_str_ptr = 0x62fdd8
Copy constructor called, p_str = 0x62fe28
Destructor called...
old_str_ptr = 0x62fdd8
new_str_ptr = 0x62fdd8
hello World!!
hello World!!
hello World!!
Destructor called...

Can someone explains what happens exactly at this line in main:

myStr1 = "hello World!!"

As I can see that it calls the conversion constructor (as the assignment operator is commented) and the address of "str" array is changed then what I don't understand is that the destructor is called and the address is returned back as seen in the output.

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
Mohamed Hossam
  • 33
  • 1
  • 1
  • 4
  • Please post the output of your program in text form instead of an image. – R Sahu Apr 28 '20 at 20:01
  • 1
    You don't have copy constructor in your code, the `String( char s[] )` is conversion constructor not copy constructor. – metablaster Apr 28 '20 at 20:01
  • @metablaster: yes sorry my mistake.. ok I can't understand the behavior still with using the conversion constructor – Mohamed Hossam Apr 28 '20 at 20:03
  • `String myStr1 = "Hello Hello";` is [copy initialization](https://en.cppreference.com/w/cpp/language/copy_initialization) but it doesn't necessarily have to make a copy or use the assignment operator. Code is a description of behaviour, not a list of instructions, and the modern compiler is a smart tool. If it sees a path to give the requested behaviour, a `String ` containing "Hello Hello", with less work, it'll take it. – user4581301 Apr 28 '20 at 20:07
  • 1
    @RSahu: I edited the question to have the output in text also edited "copy constructor" to "conversion constructor" – Mohamed Hossam Apr 28 '20 at 20:08
  • @user4581301 It cannot be copy-initialization because `myStr1` was previously-declared. This is copy/move-assignment from a temporary. – cdhowie Apr 28 '20 at 20:08
  • Ja. just spotted the `myStr1 = "hello World!!";` later in the code. Wasn't paying enough attention. – user4581301 Apr 28 '20 at 20:09
  • @user4581301: Okay I was asking about this line ```myStr1 = "hello World!!";```. as I don't understand the behavior exactly. – Mohamed Hossam Apr 28 '20 at 20:12
  • It's all my mistake, Mohamad. The only reason I've left the comments up there is removing them makes cdhowie's comment on the mistake look goofy. – user4581301 Apr 28 '20 at 20:25
  • @user4581301: no worries :).. thx for your answer – Mohamed Hossam Apr 28 '20 at 20:36

1 Answers1

1

In myStr1 = "hello World!!"; the two types are not compatible, so assigment would normally not be possible. However, the compiler notices that you have an implicit conversion constructor that accepts a pointer-to-char and so it invokes this constructor to create a temporary object from which assignment can happen. This is the sequence of events:

  • A temporary String object is constructed, invoking String("hello World!"").
  • The temporary is either copy-assigned (C++ < 11) or move-assigned (C++ >= 11) to myStr1 (you don't overload assignment so you don't observe this step in your output). Whether copy-assigned or move-assigned, the relevant compiler-generated assignment operator performs a simple memberwise value copy in this case since the members can't be moved.
  • The temporary is destructed.

The location of str does not change simply because it can't: it's an array member of the class, meaning its storage is directly allocated as part of String objects. str evaluated as a pointer points to a region of memory within the String object.

The implicit move-assignment operator simply copies the contents of the source object's str array into the target object's str array.

You see a different value on the second constructor message because this is a different object being constructed, and therefore its str member exists in a different memory location. However, this object is destructed after its value is copied into the myStr1 object.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Some of this isn't really true since C++17, in which the rules of temporary materialisation are more aligned with the abstract machine that C++ is than some sequence of steps on a physical computer. In C++17, the temporary in step one doesn't get constructed then copy/move-assigned; it literally comes into existence inside the assignment operator. – Asteroids With Wings Apr 28 '20 at 20:11
  • @AsteroidsWithWings From what I'm reading, temporary materialization would occur during copy-initialization, but that is not what is happening here. `myStr1` is already initialized. Unless I'm missing something, the elision that temporary materialization seeks to facilitate is therefore not possible in this situation. – cdhowie Apr 28 '20 at 20:20
  • It's not "elision". It's a delay. https://stackoverflow.com/questions/44146960/temporary-materialization-in-c17?noredirect=1&lq=1#comment75311600_44146960 – Asteroids With Wings Apr 28 '20 at 20:22
  • Ah, so in this case it's because the temporary is being bound to the reference argument of the move constructor. Then it sounds like the first two steps would simply be swapped, or rather the first becomes part of the second: the operator is invoked, then the temporary is materialized to facilitate the move. It doesn't sound like anything is actually elided in this case as the compiler-generated move-assignment operator is not further moving the argument, but instead reads from its members. – cdhowie Apr 28 '20 at 20:30
  • Yeah, it's not a big change, just saying ;) – Asteroids With Wings Apr 28 '20 at 20:31
  • Right. So the benefit would be where such a move operator takes the argument and passes it as an rvalue reference somewhere else (move-construction or move-assignment) in which case the intermediate move-construction is not only guaranteed to be elided, but even no move-constructor is required to exist. – cdhowie Apr 28 '20 at 20:33
  • @MohamedHossam I added a section explaining why you see the pointer values that you do. – cdhowie Apr 28 '20 at 20:37
  • @cdhowie: Ok got it.. again thanks for this clarification – Mohamed Hossam Apr 28 '20 at 20:42