-3

Mostly all things explained by fredoverflow(user 237K Rep.) in his Two answers

But while implementing Move constructor and overloaded Move Assignment operator(OMAO)( I am using these short form throughout the question ) I am facing some problem that I will put here.

Also there is another answer by user Greg Hewgill (user with 826K Rep.) https://stackoverflow.com/a/3106136/11862989his
I am Quoting him,

Suppose you have a function that returns a substantial object then an ordinary C++ compiler will create a temporary object for the result of multiply(), call the copy constructor to initialize r, and then destruct the temporary return value. Move semantics in C++0x allow the "move constructor" to be called to initialize r by copying its contents, and then discard the temporary value without having to destruct it.

I will also refer this in question.

okay Now I will start

Code

.cpp

#include"34_3.h"
#include<iostream>
#include<conio.h>
#include<cstring>

A::A()                                       // O arg ctor
{
    std::cout<<"0 arg constructor\n";
    p=0;
    s=nullptr;
        
}

A::A(int k1,const char *str)                 // 2 arg ctor
{
    std::cout<<"2 arg constructor\n";
    
    p=k1;
    s=new char[strlen(str)+1];
    strcpy(s,str);
    
}

A::A(const A &a)                             // copy ctor
{
    std::cout<<"copy constructor\n";
    
    p=a.p;
    s=new char[strlen(a.s)+1];
    strcpy(s,a.s);
    
}

A::A(A &&a)                                   // Move ctor
{
    std::cout<<"Move constructor\n";
    
    p=a.p;
    s=new char[strlen(a.s)+1];
    strcpy(s,a.s);
    a.s=nullptr;
    
}


A& A::operator=(const A &a)                 // Overloaded assignement opeator `OAO`
{
    std::cout<<"overloade= operator\n";
    
    p=a.p;
    s=new char[strlen(a.s)+1];
    strcpy(s,a.s);
    return *this;
    
}


A& A::operator=(A &&a)                        // `OMAO`
{
    std::cout<<"Move overloade = operator\n";
    
    p=a.p;
    s=new char[strlen(a.s)+1];
    strcpy(s,a.s);
    a.s=nullptr;
    return *this;
    
}

A::~A()                                       // Dctor
{
    delete []s;
    std::cout<<"Destructor\n";
    
}

void A::display()
{
    std::cout<<p<<" "<<s<<"\n";
    
}

.h

#ifndef header
#define header

struct A
{
    private:
        int p;
        char *s;
    public:
        A();                            //  0 arg ctor
        A(int,const char*);             //  2 arg ctor
        A(const A&);                    //  copy ctor
        A(A&&);                         //  Move ctor
        
        A& operator=(const A&);         // `OAO`
        A& operator=(A&&);              // `OMAO`
        
        ~A();                           // dctor
        
        void display(void);
        
};
#endif

I am putting few main functions and their outputs here so I can discuss the problem easily.

1_main

A make_A();
int main()
{
    A a1=make_A();
    
    a1.display();
    
}
A make_A()
{
    A a(2,"bonapart");
    return a;
    
}

Output

2 arg constructor
2 bonapart
Destructor
  1. why it is not executing Move constructor but if I commented out Move constructor definition in .cpp file and declaration in .h file then it give error [Error] no matching function for call to 'A::A(A)' and if I use this A a1=std::move(make_A()); then Move constructor calls, So why this happening ?
  2. Why destructor for object a in make_A() function is not running ?

2_main()

A make_A();
int main()
{
    A a1;
    a1=make_A();
    
    a1.display();
    
}
A make_A()
{
    A a(2,"bonapart");
    return a;
    
}

Output

0 arg ctor
2 arg ctor
Move overloade = operator
copy ctor
Dctor
Dctor
2 bonapart
Dctor
  1. Now here copy constructor and destructor runs for temporary object created due to return *this from Move overload = operator function. According to Greg Hewgill statement C++ 0x allows Move constructor to be called to initialize by copying it's contents and then discard the temporary value without having to destruct it. I am using C++11 but still initializing is done by creating temporary object, copy constructor.
  2. I am not getting for which object that 2nd destructor is running?

3_main

fredoverflow (user 237K Rep.) kept return type of Move overloaded operators A& but I think it is wrong.

A make_A();
int main()
{
    A a1,a2;
    a2=a1=make_A();
    
    a1.display();
    a2.display();
    
}
A make_A()
{
    A a(2,"bonapart");
    return a;
    
}

Output

[Error] prototype for 'A& A::operator=(A&&)' does not match any in class 'A'

so I feel return type should be A&& or A but A&& too give error [ERROR] can't bind a lvalue to a&&

so return type must be A, am I right ?

4

In Move constructor and Move overloaded = operator I used a.s=nullptr; This statement is always used in Move semantics fredoverflow(user) explained something like "now the source no longer owns the object it" but I am not getting it. Because if I did not write this statement still no problem everything works fine. please explain this point

Abhishek Mane
  • 619
  • 7
  • 20
  • *I am not getting for which object that 2nd dctor is running?* -- Print the value of `this`, not just `"ctor"` or `"dtor"`. The `this` value will give you a much better indication of what object is printing those lines. – PaulMcKenzie May 31 '21 at 10:49
  • 1
    In your "move" constructor and assignment operators you have the statement `a.s=nullptr;`. That will lead to a *leak* since the memory allocated for `a` is not longer free'd. You should "move" the pointers instead, not reallocate and copy (that's what the copy constructor and assignment operators do). For example by just swapping the pointers: `std::swap(s, a.s);` (But remember to initialize `s` to `nullptr` *first* in the constructor). – Some programmer dude May 31 '21 at 10:52
  • 2
    do you want an anw or an answer ? ;) Please don't use abbreviations all over the place – 463035818_is_not_an_ai May 31 '21 at 10:53
  • @PaulMcKenzie it's printing address of object `a1` by which we called Move overloaded operator but what that means – Abhishek Mane May 31 '21 at 11:04
  • 1
    1_main Move constructor is not executed due to copy elision. Try either `A a1(std::move(make_A()));` or `A a1=std::move(make_A());` to make it called explicitly – nhatnq May 31 '21 at 11:13
  • @nhatnq you are right but now extra destructor is also running, for whom it is running now ? – Abhishek Mane May 31 '21 at 11:21
  • try to start by just using one definition of A and understanding it first. 1_main: Are you compiling for debug with no optimizations? 2_main: what's even capable of printing dctor? 3_main: syntax for move = operator is a2=std::move(a1) – Abel May 31 '21 at 11:28
  • @Someprogrammerdude yeah you are right. I did not checked your comment at that time. I think I don't have clear understanding of Move semantics. I read all by fredoverflow(user 237k Rep) on Move semantics. I am not getting why destructor is not running for object `a` which is declared in `Make_A()` fun. some guy commented me that is extension of life, https://en.cppreference.com/w/cpp/language/copy_elision NRVO happens but I did not understand it. and you mentioned about `swap` there is no another way to do this ? – Abhishek Mane Jun 02 '21 at 12:08
  • @Someprogrammerdude `A a1=std::move(make_A())` will call the Move constructor but why `A a1=make_A(); not calls the Move constructor but actually it stores that value when I check it by display function. I listened about `std::move` first time can you explain it please or give some link where it explained. – Abhishek Mane Jun 02 '21 at 12:12
  • 1
    You should just ask your question without all the preamble and waffle about other questions and answers – M.M Jun 02 '21 at 21:23
  • @M.M Noted. From next time I will take care of this. – Abhishek Mane Jun 03 '21 at 02:56
  • 1
    One question per question please. Also, the first "sub-question" is a duplicate of [What are copy elision and return value optimization?](https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization/12953150#12953150) – JaMiT Jun 05 '21 at 19:01
  • @JaMiT Then for every Question I have to put this lengthy code of .h file and .cpp file – Abhishek Mane Jun 06 '21 at 05:37
  • 1
    @AbhishekMane First off, so what? Are you incapable of copy-paste-edit? Second, no, you do not have to put this lengthy code in each question. You should instead come up with simpler, more focused code. For example, your questions do not refer to `display()`, so drop that. This allows dropping all data members from `A`, which in turn makes the member functions so simple that they might as well be defined inline. *Poof!* No more .cpp file. – JaMiT Jun 07 '21 at 03:45
  • @JaMiT right. I will do it from next time. – Abhishek Mane Jun 07 '21 at 05:13

2 Answers2

1

Your class A has several issues:

  • Your assignment operator don't handle self assignment and leak:

    A& A::operator=(const A& a)
    {
        std::cout<<"overload operator=\n";
        if (this != &a)
        {
            p = a.p;
            delete[] s;
            s = new char[strlen(a.s) + 1];
            strcpy(s, a.s);
        }
        return *this;
    }
    
  • Your move doesn't move but copy:

A::A(A&& a) : p(a.p), s(a.s)
{
    a.s = nullptr;
    std::cout << "Move constructor\n";
}

A& A::operator=(A&& a)
{
    std::cout << "Move overload operator=\n";

    if (this != &a) {
        p = a.p;
        delete [] s;
        s = a.s;
        a.s = nullptr;
    }
    return *this;
}

Now, about

A make_A()
{
    A a(2,"bonapart"); // Constructor
    return a;
}

There are several scenario because of potential copy elision (NRVO) (gcc has flag as -fno-elide-constructors to control that)

if NRVO apply, then a is construct "in-place" so no extra destruction/move happens;

else there is a move constructor and the destruction of a.

A make_A()
{
    A a(2,"bonapart"); // #2 ctor(int const char*)
    return a; // #3 move (elided with NRVO)
} // #4 destruction of a, (elided with NRVO)

int main()
{
    A a1; // #1: default ctor
    a1 = // #5: Move assignment (done after make_A)
      make_A(); // #6: destructor of temporary create by make_A

    
    a1.display();
} // #8: destructor of a1

With NRVO

default ctor
ctor(int const char*)
move assignment
destructor
display
destructor

without NRVO (-fno-elide-constructors)

default ctor
ctor(int const char*)
move ctor
destructor
move assignment
destructor
display
destructor

Demo

For

A a1,a2;
a2 = a1 = make_A();

a1 = make_A(); use move assignment. a2 = (a1 = make_A()) use copy assignment as move assignment returns (correctly) A&

4 In Move constructor and Move overloaded = operator I used a.s=nullptr; This statement is always used in Move semantics fredoverflow(user) explained something like "now the source no longer owns the object it" but I am not getting it. Because if I did not write this statement still no problem everything works fine. please explain this point

Your issue is that you do copy instead of move.

If you do s = a.s; instead of the copy

s = new char[strlen(a.s) + 1];
strcpy(s, a.s);

then both this->s and a.s would point of same data, and both this and a would free the (same) memory in their destructor -> double free error.

a.s = nullptr; would fix that issue.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thanks, I am understanding your answer it will take some time as to understand I have to learn some new points you mentioned in your answer. I mentioned this comment to know you that I am checking answer but not ignoring. okay. – Abhishek Mane Jun 02 '21 at 15:35
  • 1
    Just a note: I believe any assignment operator requiring a self-assignment check to work “correctly” is still broken! The example above is no exception: if the allocation after `delete[] s;` fails with an exception, the object is left in an unrecoverable state. I’d generally recommend leveraging the copy constructor, `swap()` and, destructor: `A(a).swap(*this);` If you pass the parameter by value it is even easier: `A& operator=(A a)( a.swap(*this); return *this; }`. In this case `swap` is simple (`void swap(A& a){ std::swap(a.s, this->s); }`) but I’d still keep the pattern. – Dietmar Kühl Jun 02 '21 at 17:54
  • @Jarod42 __1.__ with NRVO you displayed output the 4th statement in that output is destructor for whom that destructor is running ? is it running for rvalue returned by `a` from `make_A()` function. Also you mentioned something like `life time extension` can you put some light on this . – Abhishek Mane Jun 03 '21 at 05:24
  • @Jarod42 __2.__ regarding returned type of Move overloaded Assignment operator. I got your point `A&` works fine. So which one is good or which one should I prefer `A` or `A&` as both work perfectly. – Abhishek Mane Jun 03 '21 at 05:25
  • @Jarod42 __3.__ I think you missed my 1st Question. can you explain it please. why that is happening ? – Abhishek Mane Jun 03 '21 at 05:33
  • @Jarod42 __4.__ `without NRVO (-fno-elide-constructors)` that you displayed output. on which compiler like C++11, C++14, C++17 I should check to get that output. – Abhishek Mane Jun 03 '21 at 05:35
  • 1
    I numbered with `#n` to show from where display happens. #4 is the destruction of local variable `a`. lifetime extension is when you have `const T& ref = temporary;`, then temporary is destroyed when `ref` goes out of scope instead of at the end of statement. Prefer `A& operator=`. Q1 about "missing" constructor/destructor is because of NRVO. (Prefer to focus on one question by question BTW). You can see version in Demo. Notice that C++17 has some required elision (for RVO) which are optional in previous version. – Jarod42 Jun 03 '21 at 08:35
  • @Jarod42 see `A a1; a1=make_A();` so that is actually like ` a1.operator=(make_A() );` right. so `make_A()` returning rvalue of local variable in it's function not reference. That rvalue is passed as an argument and collected by rvalue reference ` A &&a` in Move overloaded=operator. Now after `return *this` statement in Move overload=operator again control back to main function so when will actually `A &&a` get out of scope. Here I feel it's going out of scope before the end of statement, am I right ? again one point, is temporary value loose his own identity after it get rvalue reference ? – Abhishek Mane Jun 03 '21 at 13:01
  • @Jarod42 loose it's identity in sense it's destruction totally depend upon reference even that reference scope ends before that statement where that temporary object is present. and you mentioned `const T& ref=temporary;` I think it should be `T&&` as we are referring to rvalue. – Abhishek Mane Jun 03 '21 at 13:03
  • In regular case (no lifetime extension)`/*A val =*/ makeA();` creates a temporary which is destroyed at end of full expression (at `;`). With lifetime extension `const A& a = makeA();` or `A&& a = makeA();` the temporary is destroyed when `a` goes out of scope. – Jarod42 Jun 03 '21 at 13:11
  • @Jarod42 but `a1=make_A()` the rvalue returned by `make_A()` function is referred by rvalue reference in __Move overload=operator.__ so here also we are referring to rvalue, aren't we ? and sorry if my doubts seem silly to you but it's very hard to digest them. – Abhishek Mane Jun 03 '21 at 14:01
  • `A::operator=(A&& rhs)` will indeed "use" the temporary (`rhs` is a (rvalue) reference, so doesn't call destructor) but (moved-from) temporary cretaed by `makeA` would call its destructor. – Jarod42 Jun 03 '21 at 14:42
  • @Jarod42 but you mentioned that when `rvalue reference` get out of scope then temporary is destroyed. – Abhishek Mane Jun 03 '21 at 15:01
  • 1
    With lifetime extension (which doesn't happen with function parameter). – Jarod42 Jun 03 '21 at 15:02
  • @Jarod42 in Q 1_main I updated little bit if I use this `A a1=std::move(make_A());` instead of `A a1=Make_A();` then Move constructor. Why this is happening and I am not understanding anything about `NRVO` and `(-fno-elide-constructors)` can you explain it please elaborately or give link where it explained properly. – Abhishek Mane Jun 03 '21 at 17:16
  • @Jarod42 please do reply – Abhishek Mane Jun 04 '21 at 08:49
  • @Jarod42 I am Accepting your answer and giving bounty as you covered most of my doubts. I think I did not have sufficient knowledge of Move semantics when I will get enough time I will read it thoroughly. let it be. Thanks. – Abhishek Mane Jun 09 '21 at 12:13
0

1_main: Move constructor is not executed due to copy elision The extra destructor is observed after the swap is done, the temporary object is destroyed.

2_main: Same observation as in 1_main where move operator is called

3_main: The error is seen because you're using a low version of compiler. might need to specify -std=c++11

4: a.s=nullptr is not a case of move as you're allocating new memories and do kind of copy. For example

A::A(A &&a)                                   // Move ctor
{
    std::cout<<"Move constructor\n";       
    p=a.p;
    s=a.s;
    a.s=nullptr; 
    a.p=0;
}
nhatnq
  • 1,173
  • 7
  • 16