/* ** $Id: copying.cc 621 2007-03-23 03:32:52Z phf $ ** ** Copy constructors and assignment operators in C++, very ** important stuff. ** ** When do you need these? When the default ones don't work! ** The default operations simply copy the object in question ** literally, presumably using memcpy() or something similar. ** You often don't want this, two obvious examples: ** ** - if you hold a pointer to other objects, and you don't ** want those pointers shared between yourself and copies ** of yourself ** ** - if you have fields that should be "cleared" for every ** "fresh" copy, for example fields that count the number ** of times the object has performed a certain operation ** ** There are other cases as well, but the point is that you ** need to be able to "interfere" with the default copying, ** at least some of the time. ** ** It's a little confusing to figure out when exactly the ** copy constructor is called and when exactly the assignment ** operator is called. To make things worse they both have ** very similar jobs. In any case, the "rule of thumb" is: ** ** - if you make a new object and initialize it right away, ** the copy constructor is called ** ** - if you assign to an already existing object, however, ** the assignment operator is called ** ** We'll illustrate that below for several difference cases. ** Note that the example code is not very realistic, but it ** does show most of the relevant points. Enjoy! :-) */ #include #include using std::cout; using std::endl; // First we deal with a completely abstract example, simply // to show what gets called when. Read through class Probe, // then scroll down to the main() function, skipping Bla for // now... Oh, and please ignore the warnings you get here, // they don't matter one bit for the example. class Probe { public: Probe() { cout << "Default constructor!" << endl; } Probe(int i) { cout << "Custom constructor!" << endl; } Probe(const Probe &that) { cout << "Copy constructor!" << endl; } const Probe& operator=(const Probe &that) { cout << "Assignment operator!" << endl; return *this; } }; // The following class is *slightly* more realistic as it // can actually do stuff with it. Bla objects have a name // and count how often they have been copied. Simple. :-) class Bla { int _count; std::string _name; public: // A new object has a count of 0 and the default name // "Peter" for some strange reason. See initialize.cc // for details about the strange : notation we use. Bla(): _count(0), _name("Peter") { std::cout << "Default constructor!" << std::endl; } // Sometimes we want objects with a different name, // so we have a second constructor. Bla(std::string name): _count(0), _name(name) { std::cout << "String Constructor!" << std::endl; } // The copy constructor has to increment the count. // It also appends something to the name, just for // kicks. Note that the parameter is passed as a // constant reference to avoid additional copies! Bla(const Bla &that): _count(that._count+1), _name(that._name+"'s copy") { std::cout << "Copy constructor!" << std::endl; } // The assignment operator does very much the same // as the copy constructor, but it appends something // else so we can distinguish them. const Bla& operator=(const Bla &that) { // Note that you should normally check that you're not // assigning yourself to yourself, so the body of the // assignment operator should be inside something like // "if (this != &that) { ... } return *this;"! std::cout << "Assignment operator!" << std::endl; this->_count = that._count+1; this->_name = that._name+"'s assigned copy"; return (*this); } // Just accessors to enable us to print some stuff. Go // back into main() now! :-) int count() const { return this->_count; } std::string name() const { return this->_name; } }; // One hell of a function, takes Bla objects in various // different modes and does a funky assignment inside. void oops( Bla a, Bla &b, const Bla &c, Bla *d ) { std::cout << "--- oops incoming ---" << std::endl; cout << "a: " << a.count() << " " << a.name() << endl; cout << "b: " << b.count() << " " << b.name() << endl; cout << "c: " << c.count() << " " << c.name() << endl; cout << "d: " << d->count() << " " << d->name() << endl; std::cout << "--- oops assignment ---" << std::endl; a = b = c; // can't add "= *d" because we can't assign to c! std::cout << "--- oops outgoing ---" << std::endl; cout << "a: " << a.count() << " " << a.name() << endl; cout << "b: " << b.count() << " " << b.name() << endl; cout << "c: " << c.count() << " " << c.name() << endl; cout << "d: " << d->count() << " " << d->name() << endl; // Note: The possibility of multiple assignment is the // reason why the assignment operator has to return the // object that was assigned to! } int main() { // Simply declaring a Probe object will run the default // constructor, as is to be expected; note that the two // forms below are equivalent. std::cout << "----------" << std::endl; Probe pd; // Passing an integer will run the custom constructor // for that type, again to be expected. std::cout << "----------" << std::endl; Probe pi(10); // If we make a new Probe object and initialize it // from an existing one, the copy constructor gets // called. std::cout << "----------" << std::endl; Probe pc(pi); // If we have a Probe object already and assign // another Probe object to it, the assignment // operator gets called. std::cout << "----------" << std::endl; pd = pc; // Now things get a little strange; for the following // we *also* call the copy constructor even though it // *looks* like an assignment! std::cout << "----------" << std::endl; Probe pa = pd; // In fact, this notation is the exact same thing as // if we had written pa(pd) instead. Since that is the // case, the C++ designers noticed that there's a little // inconsistency now. In C we can write this: int i = 20; i += 1; // It will make "an int object" and initialize it, which // is exactly where the copy constructor would be called // for a real object. In C we can't write int i(20) to do // the same thing, but to make things consistent, we can // do exactly that in C++: int j(20); j += 1; // Fascinating, isn't it? Way to go C++, even more stuff // to confuse us with. But makes for great questions in // an interview I guess. Sometime you wonder if Bjarne // simply wanted to make sure he could ask lots of hard // questions when hiring people... :-) // That's it for the completely abstract examples; now // go back up and read through the Bla class, then come // back here. // Let's start by making a first Bla object and printing // it's stats: std::cout << "----------" << std::endl; Bla b; cout << b.count() << " " << b.name() << endl; // A second fresh object can't hurt, can it? :-) std::cout << "----------" << std::endl; Bla c("Paul"); cout << c.count() << " " << c.name() << endl; // Now we'll assign b to c and see how things go from // bad to worse... :-) std::cout << "----------" << std::endl; c = b; cout << b.count() << " " << b.name() << endl; cout << c.count() << " " << c.name() << endl; // Really scary stuff now, assign something to itself! std::cout << "----------" << std::endl; c = c; cout << c.count() << " " << c.name() << endl; // Let's bring in another new object, through the copy // constructor this time. std::cout << "----------" << std::endl; Bla d = c; cout << d.count() << " " << d.name() << endl; // There's a function oops() defined above, read it // now, then return here. std::cout << "--- about to call oops ---" << std::endl; oops(d, d, d, &d); // Note that "d" actually changed since it was passed // by reference, but only to a count of 4. Figure out // why oops() is printing a count of 5 for one... :-) std::cout << "--- back from oops ---" << std::endl; cout << d.count() << " " << d.name() << endl; // That's it. Try to get some sleep! :-) return 0; }