problems with Move constructor and Move overloaded assignment operator?
Asked Answered
Y

2

-2

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://mcmap.net/q/15702/-what-is-move-semanticshis
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

Yolandayolande answered 31/5, 2021 at 10:46 Comment(15)
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.Caryncaryo
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).Gingerly
do you want an anw or an answer ? ;) Please don't use abbreviations all over the placeAvila
@Caryncaryo it's printing address of object a1 by which we called Move overloaded operator but what that meansYolandayolande
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 explicitlyInterlard
@Interlard you are right but now extra destructor is also running, for whom it is running now ?Yolandayolande
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)Dichogamy
@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, 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 ?Yolandayolande
@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.Yolandayolande
You should just ask your question without all the preamble and waffle about other questions and answersEdentate
@Edentate Noted. From next time I will take care of this.Yolandayolande
One question per question please. Also, the first "sub-question" is a duplicate of What are copy elision and return value optimization?Torp
@Torp Then for every Question I have to put this lengthy code of .h file and .cpp fileYolandayolande
@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.Torp
@Torp right. I will do it from next time.Yolandayolande
C
2

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.

Corniculate answered 2/6, 2021 at 13:38 Comment(17)
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.Yolandayolande
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.Trant
@Corniculate 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 .Yolandayolande
@Corniculate 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.Yolandayolande
@Corniculate 3. I think you missed my 1st Question. can you explain it please. why that is happening ?Yolandayolande
@Corniculate 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.Yolandayolande
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.Corniculate
@Corniculate 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 ?Yolandayolande
@Corniculate 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.Yolandayolande
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.Corniculate
@Corniculate 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.Yolandayolande
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.Corniculate
@Corniculate but you mentioned that when rvalue reference get out of scope then temporary is destroyed.Yolandayolande
With lifetime extension (which doesn't happen with function parameter).Corniculate
@Corniculate 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.Yolandayolande
@Corniculate please do replyYolandayolande
@Corniculate 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.Yolandayolande
I
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;
}
Interlard answered 31/5, 2021 at 12:8 Comment(3)
Constructors do not have return typesEdentate
the commented-out lines should be in , otherwise destruction of old object will leave dangling pointer in the newEdentate
@Edentate I got it but wanted to leave an undefined behavior so commented them out ,will undo themInterlard

© 2022 - 2024 — McMap. All rights reserved.