Thread: Copy constructors and operator=()

  1. #1
    Registered User filler_bunny's Avatar
    Join Date
    Feb 2003
    Posts
    87

    Copy constructors and operator=()

    I have been reading many texts on these two components of an object, but I am not 100% convinced I understand them.

    But this is what I think I have:

    1) A copy constructor is provided by default, but should be overided in the case you require "deep" copying of your objects data members (i.e. you have allocated memory to pointers and you want the values copied not the pointer).
    2) If you provide your own copy constructor you should also provide your own operator= method.

    I presume that the reason for 2 is that if you have to "deep" copy with the copy constructor, then the same holds true for any assignment...

    [EDIT]
    I think I have this around the wrong way.. If I define a operator=(), THEN I must have a copy constructor..
    [/EDIT]

    But I am not sure I fully understand the purpose of 2. Can anyone enlighten me or correct me?
    Last edited by filler_bunny; 08-24-2003 at 03:40 AM.
    Visual C++ .net
    Windows XP profesional

  2. #2
    Registered User
    Join Date
    Apr 2003
    Posts
    2,663
    Presumably if you are defining either a copy constructor or an assignment operator, you want the identical functionality in the other one, so if you implement one, you are most likely going to want to implement the other.

    Just remember: A copy constructor is called when:

    1) A class object is created and intialized with an existing object of the same class, e.g.:

    MyObject A;

    MyObject B(A); //copy constructor called

    //this one's tricky but the copy constructor is also called in this case--not the assignment operator:

    MyObject C = A;

    2) the copy constructor is called when an object is passed to a function by value.

    The assignment operator is called whenever you see an equals sign after the object(execept for that one case above).
    Last edited by 7stud; 08-24-2003 at 04:53 AM.

  3. #3
    Veni Vidi Vice
    Join Date
    Aug 2001
    Posts
    343
    The copy constuctor and assignment operator are close related with each other(they usaully do the same operation). The only time you actually need to define your own version is when your class have pointer members (but it is considered good programming practice to implement them every time).
    01000111011011110110111101100100 011101000110100001101001011011100110011101110011 01100100011011110110111001110100 01100011011011110110110101100101 01100101011000010111100101110011 0110100101101110 01101100011010010110011001100101
    Good things donīt come easy in life!!!

  4. #4
    Registered User filler_bunny's Avatar
    Join Date
    Feb 2003
    Posts
    87
    So essentially the only difference in the implementation of the two is really the fact that the operator=() returns a reference to itself (Of course the signatures are different).

    Is that correct?
    Visual C++ .net
    Windows XP profesional

  5. #5
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Well, not exactly. operator= has two functions, to destroy the old contents of the object, and to copy the contents of the new object. In a way, you often want the functionality of both destructor and copy constructor.

    Personally, I write all my classes so they have a private nothrow swap() method. As long as you have a way to swap the contents of two objects that cannot throw an exception, it's very easy to implement assignment. You implement all the details in the copy constructor and destructor, and then your assignment operator is just:

    Code:
    //This first line (which has other passed by copy guarantees that the 
    //copy constructor is called to create other
    MyClass& MyClass::operator=(MyClass other) {
      swap(other); 
      //Now this has the new data, other has the old data (to be deleted)
      return *this;
    } //Here, other is deleted as it goes out of scope.
    So this code actually does call the copy constructor and destructor, albeit both implicity.

    The nice part is, there is 0% duplicated code. So if I change how an object is copy constructed, or destructed, I change it once.
    Last edited by Cat; 08-24-2003 at 09:51 AM.

  6. #6
    Registered User
    Join Date
    Feb 2002
    Posts
    465
    Originally posted by ripper079
    The copy constuctor and assignment operator are close related with each other(they usaully do the same operation). The only time you actually need to define your own version is when your class have pointer members (but it is considered good programming practice to implement them every time).

    actually, one of the most important reasons for implimenting the copy constructor and assignment operator is that when your class uses dynamic memory, the default copy constructor and assignment operator merely do a member to member copy and dont delete old memory. youll get a memory leak if you dont define your own.
    I came up with a cool phrase to put down here, but i forgot it...

  7. #7
    Veni Vidi Vice
    Join Date
    Aug 2001
    Posts
    343
    Cat has showed you a way to implement a copy constructor in a very generic way on the cost of effetiveness. In this case it might not be noticible but when a class grows in size there is better ways of doing it. Also there should be some kind of test to see if you are assigning an object to the same object.
    Code:
    if (this == &other)
    ;
    else
    {
    }
    actually, one of the most important reasons for implimenting the copy constructor and assignment operator is that when your class uses dynamic memory, the default copy constructor and assignment operator merely do a member to member copy and dont delete old memory. youll get a memory leak if you dont define your own.
    I agree. But it goes futher, study this code snippet

    Code:
    #include <iostream>
    
    using namespace std;
    
    class foo
    {
    public:
    	foo(int pnumb);
    	~foo();
    
    	int getNumber() const;
    	int *getPointerAddress() const;
    
    	void changeNumber(int pNumb);
    private:
    	int *ptest;
    	int number;
    };
    
    foo::foo(int pnumb)
    {
    	number = pnumb;
    	ptest = &number;
    }
    
    foo::~foo()
    {}
    
    int foo::getNumber() const
    {
    	return number;
    }
    
    int* foo::getPointerAddress() const
    {
    	return ptest;
    }
    
    void foo::changeNumber(int pNumb)
    {
    	number = pNumb;
    }
    
    int main()
    {
    
    	foo test1(70);
    	foo test2(90);
    
    	cout << "BEFORE assigment operator is invoked" << endl;
    	cout << "test1" << endl;
    	cout << "number: " << test1.getNumber() << endl;
    	cout << "*ptest: " << test1.getPointerAddress() << endl << endl;
    	cout << "test2" << endl;
    	cout << "number: " << test2.getNumber() << endl;
    	cout << "*ptest: " << test2.getPointerAddress() << endl << endl << endl << endl;
    
    	/* Assignment operator invoked - We want a copy of it*/
    	test2 = test1;
    
    	cout << "AFTER assignment operator is invoked" << endl;
    	cout << "test1" << endl;
    	cout << "number: " << test1.getNumber() << endl;
    	cout << "*ptest: " << test1.getPointerAddress() << endl << endl;
    	cout << "test2" << endl;
    	cout << "number: " << test2.getNumber() << endl;
    	/* Oops pointing to wrong variable */
    	cout << "*ptest: " << test2.getPointerAddress() << endl << endl << endl << endl;
    
    return 0;
    }
    As you can ses the pointer variable for test2 is pointing at numbervariable in object test1. Ouchhhhh.
    01000111011011110110111101100100 011101000110100001101001011011100110011101110011 01100100011011110110111001110100 01100011011011110110110101100101 01100101011000010111100101110011 0110100101101110 01101100011010010110011001100101
    Good things donīt come easy in life!!!

  8. #8
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Originally posted by ripper079
    Cat has showed you a way to implement a copy constructor in a very generic way on the cost of effetiveness. In this case it might not be noticible but when a class grows in size there is better ways of doing it.
    Actually, the way I propose is the single most efficient means of *safely* accomplishing the task at hand. Anything less and you lose the strong exception safety guarantee. To have strong exception safety, operator= must:

    1) Create temporary storage for all variables that will change
    2) Attempt to set the temporary variables to the desired final data.
    3) Set the primary variables to the new data (this step also MUST not fail)
    4) Delete the old data.

    The model I propose (you can inline swap() for better performance if desired) accomplishes these in a simple way with zero duplication of code. 1 and 2 are accomplished by the local copy which is passed, 3 is accomplished by the nothrow swap(), and 4 is accomplished as the local object goes out of scope. It only truly wastes memory when an object with a large vtable (lots of virtual functions) is created, and in general, the benefits of simplicity, of not duplicating code, and of maintaining strong exception safety still outweigh the minor performance hit from the vtable.

    Also there should be some kind of test to see if you are assigning an object to the same object.
    Code:
    if (this == &other)
    ;
    else
    {
    }
    You can certainly make this test, but the ONLY reason to do this should be for performance. If your assignment operator would FAIL from self-assignment without the check, there's a 95% chance your assignment operator is horribly exception-unsafe to begin with. In most of my code, I leave out the self-assignment check, because in reality, putting it in is a performance hit in and of itself -- if statements are usually bad at performance because the CPU must do branch prediction, and if it fails, you lose CPU time as the instructions that are in-flight are purged and the correct instructions are executed. So I prefer to take a small performance hit on self-assignment (which is rare) in order to avoid a hit on all assignments.

    I figure, even if I'm putting my objects in STL containers that might occasionally self-assign, I imagine self-assignments will typically acount for < 1% of all my assignments (it's easy enough to write some additional code that tracks and quantifies assignments). I don't think that making 99% of my assignments less efficient to make 1% more efficient is a good tradeoff in most cases.
    Last edited by Cat; 08-24-2003 at 04:02 PM.

  9. #9
    Veni Vidi Vice
    Join Date
    Aug 2001
    Posts
    343
    Maybe I should have said BIG classes insteed of 'class grows in size'. I believe that you have some intressting points here, but some still confuses me. I canīt se how this is less *saftly* and inefficient.

    Code:
    Employee& Employee::operator=(const Employee &rhs)
    {
    if (this != &rhs)
    {
    mname = rhs.mname;
    mmoney = rhs.mmoney;
    }
    return *this;
    }
    Even if something would fail example not enough memory could be set aside for mname what makes you version safer. If you could illustrate with an example it would certainly make it easier for me. And in the aspect of performance even if you would make it inline you would still need to allocate memory for your otherobject. This means that the copyconstructor + destructor would need to be called every time you invoke that function. And if the otherobject would have datamember that implements an equal operator= function the same procees would start again. If you look closer at 'mine' version you are manipulating datamember directly and therefore no need to invoke the copyconstructor and the destructor.

    In most of my code, I leave out the self-assignment check, because in reality, putting it in is a performance hit in and of itself -- if statements are usually bad at performance because the CPU must do branch prediction, and if it fails, you lose CPU time as the instructions that are in-flight are purged and the correct instructions are executed
    I figure, even if I'm putting my objects in STL containers that might occasionally self-assign, I imagine self-assignments will typically acount for < 1% of all my assignments (it's easy enough to write some additional code that tracks and quantifies assignments). I don't think that making 99% of my assignments less efficient to make 1% more efficient is a good tradeoff in most cases.
    I can agree that it isnīt often you make an self-assigment to an object but I find it very hard to belive that a simple if-statements(that compare a memory address) is actually slower(in CPU power) than writing to memory(small as HUGE classes). I donīt have any numbers of it so I canīt confirm it.
    01000111011011110110111101100100 011101000110100001101001011011100110011101110011 01100100011011110110111001110100 01100011011011110110110101100101 01100101011000010111100101110011 0110100101101110 01101100011010010110011001100101
    Good things donīt come easy in life!!!

  10. #10
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    The writing to another, temporary workspace is not really negotiable in terms of exception safety.

    Take this for example:

    Code:
    Screen & Screen::operator=(const Screen &rhs){
    	if (this != &rhs){
    		delete[] backBuffer;
    		delete[] frontBuffer;
    		backBuffer = new unsigned char[rhs.bufferSize];
    		frontBuffer = new unsigned char[rhs.bufferSize];
    		memcpy(backBuffer,rhs.backBuffer,rhs.bufferSize);
    		memcpy(frontBuffer,rhs.frontBuffer,rhs.bufferSize);
    		bufferSize = rhs.bufferSize;
    	}
    }
    This has no exception safety at all. What if "backBuffer = new unsigned char[rhs.bufferSize];" throws an exception? Not only will the object be left with the assignment only partially complete, the object is left in a terrible state: it can't even be destroyed! backBuffer and frontBuffer still point to memory that has already been freed, so calling delete[] on them would instantly crash the program.

    Adding code in between the delete[] and the new[] to set both pointers to NULL would get rid of that problem, so let's suppose we do that:

    Code:
    Screen & Screen::operator=(const Screen &rhs){
    	if (this != &rhs){
    		delete[] backBuffer;
    		delete[] frontBuffer;
    		backBuffer = NULL;
    		frontBuffer = NULL;
    		backBuffer = new unsigned char[rhs.bufferSize];
    		frontBuffer = new unsigned char[rhs.bufferSize];
    		memcpy(backBuffer,rhs.backBuffer,rhs.bufferSize);
    		memcpy(frontBuffer,rhs.frontBuffer,rhs.bufferSize);
    		bufferSize = rhs.bufferSize;
    	}
    }
    Now, assume that "frontBuffer = new unsigned char[rhs.bufferSize];" throws an exception. The object is in an "impossible" state. backBuffer points to memory of a certain size (the size of rhs.bufferSize), frontBuffer is a NULL pointer, and bufferSize is meaningless because it was never updated. The object can be safely destroyed, but it can probably never again be used. This has the lowest level of exception safety: it doesn't leak memory in the face of an exception. However, when an exception happens, the object is left in an intermediate state; a state that is neither the initial nor the final state, and potentially not even a legal state.

    Copy and swap, or a functionally equivalent method, is the only way to get strong exception safety. Strong exception safety states that if an exception is thrown in a function, it will be as if the function never happened. That is, if the code succeeds, the object will be in its final state; if the code fails, the object's state is unaltered.

    In the following code:

    Code:
    Screen & Screen::operator=(Screen rhs){
    	swap(rhs);
    }
    the only thing that can fail is the copy construction. If copy construction throws, then the swap will never take place, which guarantees our object remains fully unaltered. If copy construction succeeds, we know we succeed fully, because we have coded swap such that it cannot throw, and the destructor also cannot throw for any well-written class.

    Assuming that the copy constructor can't leak memory in the face of an exception, we've achieved strong exception-safety with this function -- it either fully succeeds or it has no effect. Without making a temporary, this is impossible; the first functions, if they failed, would still have altered the object.

    If we want strong exception-safety (and we should), there is no way to avoid having another copy of the data members; at the very least, all data members which can throw exceptions. And typically, in classes with large memory needs, it is dynamically allocated memory, and thus is vulnerable to throwing.

    I can agree that it isn't often you make an self-assigment to an object but I find it very hard to belive that a simple if-statements(that compare a memory address) is actually slower(in CPU power) than writing to memory(small as HUGE classes). I don't have any numbers of it so I can't confirm it.
    But again, making a temporary is not about performance, it's about safety, and there is no way around it (except in trivially simple cases, but trivially simple cases won't need custom assignment operators). And, once we get to the strong exception safety plateau, we also will have succeeded in making checking for self-assignment optional. Given that it's now optional, I almost always opt not to, for the reasons above.

  11. #11
    Veni Vidi Vice
    Join Date
    Aug 2001
    Posts
    343
    Yes Cat, you are completly right, itīs safer. You made a great job of explaining that for me. I think this example illustrates the tradeof of safetyness/effetiveness. But as a said
    Cat has showed you a way to implement a copy constructor(show be assignment operator) in a very generic way on the cost of effetiveness. In this case it might not be noticible but when a class grows in size there is better ways of doing it.
    I was refering to effetiveness and not to safty.You could argue that wihtout saftyness effetiveness is useless, but there is cases where effetiveness is more important than saftly (e.i. in our example, the total memory availible for the app could be more than suffient (nowdays computer have > 1GB memory)).

    But to continue on the subject.
    Code:
    Screen & Screen::operator=(Screen rhs){
    	swap(rhs);
    }
    Is the term 'strong exception safety' referred to just as 'strong exception safety' and not to 'perfect exception safety' becuase the rhs object might fail to be created properly(and cause a e.i memory leak)?

    One final question just . Why are you creating a new object? I know that c++ is very object oriented but there are cases where it could/should be avoided. Why not change your code to this to actually enhance preformance at no cost of *safty* (I think).
    Code:
    Screen & Screen::operator=(const Screen &rhs){
    	if (this != &rhs)
    	{
    		unsigned char *oldbackBuffer = new unsigned char[rhs.bufferSize];
    		unsigned char *oldfrontBuffer = new unsigned char[rhs.bufferSize];
    		if (oldbackBuffer != 0 && olfrontBuffer != 0)
    		{
    			delete[] backBuffer;
    			delete[] frontBuffer;
    			backBuffer = NULL;
    			frontBuffer = NULL;
    			backBuffer = oldbackBuffer;
    			frontBuffer = oldfrontBuffer;
    			memcpy(backBuffer,rhs.backBuffer,rhs.bufferSize);
    			memcpy(frontBuffer,rhs.frontBuffer,rhs.bufferSize);
    			bufferSize = rhs.bufferSize;	
    		}
    	}
    return *this;
    }
    Well even if allocation for oldfrontBuffer would fail and cause a memory leak for oldbackBuffer I canīt se how your version wouldnīt suffer from it. With this solution you can avoid many calls to copyconstructor and destructor(especially if the class contains other classes, and that class contains an anotherclass and so on).
    01000111011011110110111101100100 011101000110100001101001011011100110011101110011 01100100011011110110111001110100 01100011011011110110110101100101 01100101011000010111100101110011 0110100101101110 01101100011010010110011001100101
    Good things donīt come easy in life!!!

  12. #12
    Registered User filler_bunny's Avatar
    Join Date
    Feb 2003
    Posts
    87
    Well, not exactly. operator= has two functions, to destroy the old contents of the object, and to copy the contents of the new object. In a way, you often want the functionality of both destructor and copy constructor.
    Ah.. I see, I guess I was forgetting that the copy constructor is only called when creating an object, and thus there are no contents to destroy.

    Your all discussing the possibility of self assignment of an object, but I am having a hard time imagining how this could happen without explicitly doing so. Can you give me an example? Cat you mention this occuring when using STL containers, under what circumstances?

    Thanks too, this is very informative.
    Visual C++ .net
    Windows XP profesional

  13. #13
    Registered User filler_bunny's Avatar
    Join Date
    Feb 2003
    Posts
    87
    Oops, got another question:
    Code:
            // Prevent copying of this object by declaring private
            // versions of the copy ctor and assignment operator
            CQueue(const CQueue &) { }
            CQueue & operator=(const CQueue & q) { return *this; }
    Creating the previous declarations as private methods; is this a valid way of preventing an object being copied? For instance if I have a queue object, I wouldn't really want to copy this under normal circumstances, so by declaring those members as private, they cannot be accessed publicly therefore a compiler error will be thrown if I accidently try to explicitly copy the objects...
    Visual C++ .net
    Windows XP profesional

  14. #14
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Originally posted by ripper079
    Is the term 'strong exception safety' referred to just as 'strong exception safety' and not to 'perfect exception safety' becuase the rhs object might fail to be created properly(and cause a e.i memory leak)?
    Not if you code the copy constructor properly. Strong exception safety assures that there will not be memory leaks.

    One final question just . Why are you creating a new object? I know that c++ is very object oriented but there are cases where it could/should be avoided. Why not change your code to this to actually enhance preformance at no cost of *safty* (I think).
    Yes, but at this point, you're essentially doing the same thing. Creating an object is, for all intents and purposes, just as "expensive" as creating the same number of temporary variables. You don't really have much additional overhead.

    And because you need to implement the functionality of both destructor and copy constructor, you really only have the additional expense of the overhead for the calls themselves. And this performance hit is usually so small as to be totally insignificant.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. A pointer question.
    By joenching in forum C++ Programming
    Replies: 7
    Last Post: 03-20-2008, 04:10 PM
  2. LinkedList operator= and deep copy
    By sethjackson in forum C++ Programming
    Replies: 11
    Last Post: 02-28-2008, 12:54 PM
  3. illegal copy constructor required?
    By ichijoji in forum C++ Programming
    Replies: 1
    Last Post: 03-08-2005, 06:27 PM
  4. Operator=
    By crzy1 in forum C++ Programming
    Replies: 4
    Last Post: 04-02-2003, 10:08 PM
  5. copy constructor
    By Unregistered in forum C++ Programming
    Replies: 2
    Last Post: 04-30-2002, 02:23 PM