Slide Set 14 for ENCM 339 Fall 2016 Steve Norman, PhD, PEng Electrical & Computer Engineering Schulich School of Engineering University of Calgary November 2016
ENCM 339 Fall 2016 Slide Set 14 slide 2/35 Contents Syntax for copy constructor and copy assignment operator functions The goal of a copy constructor The copy assignment operator The Law of the Big 3 Class objects within class objects Summary of rules for synthesized member functions
ENCM 339 Fall 2016 Slide Set 14 slide 3/35 Outline of Slide Set 14 Syntax for copy constructor and copy assignment operator functions The goal of a copy constructor The copy assignment operator The Law of the Big 3 Class objects within class objects Summary of rules for synthesized member functions
ENCM 339 Fall 2016 Slide Set 14 slide 4/35 Syntax for copy constructor and copy assignment operator functions Before we get to the syntax, let s review the roles of these class member functions. The role of a copy constructor is to handle copy initialization in cases where memberwise copy does not do the right thing. The role of a copy assignment operator is to handle copy assignment in cases where memberwise copy does not do the right thing. The next slide shows the syntax for a copy constructor and an assignment operator...
ENCM 339 Fall 2016 Slide Set 14 slide 5/35 class Foo { public: //... various ctors... Foo(const Foo& src); // copy ctor // copy assignment operator.. Foo& operator=(const Foo& rhs); //... other member functions... private: //... member variables... };
ENCM 339 Fall 2016 Slide Set 14 slide 6/35 Let s make some notes about the (straightforward) types and names of the parameters of copy constructor and the copy assignment operator.
ENCM 339 Fall 2016 Slide Set 14 slide 7/35 The name of the copy assignment operator function is operator =. This is an example of operator overloading giving a specific meaning to a C++ operator when it is used with one or more class objects. More examples of operator overloading: operator << is often overloaded to make it easy to write class object data to a stream such as cout; for a class representing some special numerical type, it makes sense to overload arithetic operators such as +, -, *, and /. Let s make a brief note about the (weird-looking) return type of operator =.
ENCM 339 Fall 2016 Slide Set 14 slide 8/35 Outline of Slide Set 14 Syntax for copy constructor and copy assignment operator functions The goal of a copy constructor The copy assignment operator The Law of the Big 3 Class objects within class objects Summary of rules for synthesized member functions
ENCM 339 Fall 2016 Slide Set 14 slide 9/35 The goal of a copy constructor A good way to understand what a copy constructor should do is to look at what goes wrong if a class lacks a copy constructor...
ENCM 339 Fall 2016 Slide Set 14 slide 10/35 Here is a code fragment from Slide Set 13: BadString s1; s1.push_back( A ); BadString s2 = s1; // s2.push_back( B ); // (1) The diagram for point (1) shows the undesirable effect of memberwise copy from s1 to s2. AR main stack s2 storagem lengthm capacitym 2 4 s1 storagem lengthm capacitym 1 4 no args heap A B \0??
ENCM 339 Fall 2016 Slide Set 14 slide 11/35 If BadString is replaced by GoodString, and GoodString has a reasonable copy constructor... GoodString s1; s1.push_back( A ); GoodString s2 = s1; // (0) s2.push_back( B );... then the diagram shows a possible state of affairs at point (0). AR main s2 s1 storagem lengthm capacitym storagem lengthm capacitym no args stack 1 2 1 4 heap A \0 A \0???? Does it matter that capacitym is not the same in the two class objects? What does matter?
ENCM 339 Fall 2016 Slide Set 14 slide 12/35 Outline of Slide Set 14 Syntax for copy constructor and copy assignment operator functions The goal of a copy constructor The copy assignment operator The Law of the Big 3 Class objects within class objects Summary of rules for synthesized member functions
ENCM 339 Fall 2016 Slide Set 14 slide 13/35 The copy assignment operator Suppose that Foo is some kind of container class, and that each Foo object manages some heap memory in which data items are stored. (Examples of containers we know about: strings, vectors. A kind of container we ll look at soon: linked lists.) Why is the copy assignment shown at right more complicated than copy initialization? Foo a, b; //... insert lots of data items into containers a and b //... b = a;
ENCM 339 Fall 2016 Slide Set 14 slide 14/35 Chained assignment In C and C++, an assignment involving a built-in type is an expression that has a value. That allows code like this: int x, y; y = x = 7; The = operator is said to associate right-to-left, which implies that the above statement really means: y = (x = 7); The expression x = 7 has two effects: first, to give x a value of 7; second, to produce a value of 7 that can be assigned to y. When writing a copy assignment operator for a class, it is good to support the same kind of chained assignment that works for built-in types.
ENCM 339 Fall 2016 Slide Set 14 slide 15/35 A quick digression on operator associativity The = operator associates right-to-left, so d = c = b = a; really means d = (c = (b = a)); But most binary operators (operators with two operands) associate left-to-right. For example, with subtraction, u = w - x - y - z; really means u = ((w - x) - y) - z;
ENCM 339 Fall 2016 Slide Set 14 slide 16/35 Self-assignment This kind of statement should be harmless: a = a; Of course, there is no reason to write code like the above statement. But consider the function to the right... If x and y happen to refer to the same single object, that should not cause failure of the program f belongs to. void f(foo& x, Foo& y) { statements } if ( condition ) x = y; more statements
ENCM 339 Fall 2016 Slide Set 14 slide 17/35 General pattern for a copy assignment operator Again, this assumes that each Foo object manages a collection of data items stored in heap memory. Foo& operator =(const Foo& rhs) { if (this!= &rhs) check for self-assignment { manage heap memory } copy data from rhs to *this } return *this; support chained assignment Let s make some notes about how this function works.
ENCM 339 Fall 2016 Slide Set 14 slide 18/35 Complete examples of copy constructors and copy assignment operators Let s change the name of BadString from Slide Set 13 to GoodString, and add a copy constructor and copy assignment operator, as shown on lines 8 and 9 on the next slide... We ll use the same memory management policy for GoodString as was used for BadString. Let s make some notes to review that memory management policy.
#ifndef GOODSTRING_H #define GOODSTRING_H class GoodString { public: GoodString() : storagem(0), lengthm(0), capacitym(0) { } GoodString(const char *s); GoodString(const GoodString& src); // line 8 GoodString& operator=(const GoodString& rhs); // line 9 ~GoodString() { delete [ ] storagem; } const char *c_str() const { return (lengthm == 0)? "" : storagem; } void push_back(char c); // Functions for length, append, element access,... private: char *storagem; int lengthm; int capacitym; }; #endif
ENCM 339 Fall 2016 Slide Set 14 slide 20/35 Here is a definition for the copy constructor: GoodString::GoodString(const GoodString& src) { if (src.lengthm == 0) { storagem = 0; lengthm = capacitym = 0; } else { lengthm = src.lengthm; capacitym = lengthm + 1; storagem = new char[capacitym]; for (int i = 0; i <= lengthm; i++) storagem[i] = src.storagem[i]; } } Let s make some notes about the copy constructor code.
ENCM 339 Fall 2016 Slide Set 14 slide 21/35 The next slide shows a definition for the copy assignment operator. It s significantly more complicated than the copy constructor code, because it has to deal with self-assignment, and has to take care of deallocation of heap memory already in use by a GoodString object. Let s make some notes about the code for the copy assignment operator.
ENCM 339 Fall 2016 Slide Set 14 slide 22/35 GoodString& GoodString::operator=(const GoodString& rhs) { if (this!= &rhs) { delete [ ] storagem; // line 3 if (rhs.lengthm == 0) { storagem = 0; lengthm = capacitym = 0; } else { lengthm = rhs.lengthm; capacitym = lengthm + 1; storagem = new char[capacitym]; for (int i = 0; i <= lengthm; i++) storagem[i] = rhs.storagem[i]; } } return *this; }
ENCM 339 Fall 2016 Slide Set 14 slide 23/35 Outline of Slide Set 14 Syntax for copy constructor and copy assignment operator functions The goal of a copy constructor The copy assignment operator The Law of the Big 3 Class objects within class objects Summary of rules for synthesized member functions
ENCM 339 Fall 2016 Slide Set 14 slide 24/35 The Law of the Big 3 In discussion of C++, the Law of the Big 3 or rule of three is a statement about these three special member functions: copy constructor copy assignment operator destructor The law says that if a class provides a definition for any one of the big three, it almost certainly needs to define the other two as well. Suppose yet again that Foo is a container class, and each Foo object has a pointer into heap memory. Let s make notes about why the Law of the Big 3 applies to the design of Foo.
ENCM 339 Fall 2016 Slide Set 14 slide 25/35 C++11 and the rule of three/five This is not material you need to know for any lab or for the final exam in ENCM 339 in Fall 2016. Section 13.1.4 in the C++ textbook recommended for ENCM 339 is titled The Rule of Three/Five. C++11 added a new kind of reference type called an rvalue reference. These types support two new special member functions called the move constructor and the move assignment operator, designed to allow efficient transfer of data from some class object A about to be destroyed to some other class object B that will live on after A is destroyed. See Section 13.6 in the textbook if you are curious about the details.
ENCM 339 Fall 2016 Slide Set 14 slide 26/35 Outline of Slide Set 14 Syntax for copy constructor and copy assignment operator functions The goal of a copy constructor The copy assignment operator The Law of the Big 3 Class objects within class objects Summary of rules for synthesized member functions
ENCM 339 Fall 2016 Slide Set 14 slide 27/35 Class objects within class objects It is common to write a C++ class type that has one or more member variables of class types. For this kind of class, C++ provides a number of helpful but somewhat complicated rules related to initialization, copy assignment, and destruction. Let s look at an example in which member variables have types from the C++ library: string and vector<double>. While we study this example, keep in mind that an object of type string manages an array of chars in heap memory; an object of type vector<double> manages an array of doubles in heap memory.
ENCM 339 Fall 2016 Slide Set 14 slide 28/35 The next slide shows a header file for a class called MarkRecordV1. The slide after that shows a program that demonstrates use of the MarkRecordV1 class. Note that MarkRecordV1 does not explicitly define any of the following functions!! default constructor copy constructor any other kind of constructor copy assignment operator destructor
#ifndef MARKRECORDV1_H #define MARKRECORDV1_H #include <vector> #include <string> using std::size_t; class MarkRecordV1 { public: const std::string& get_name() const { return namem; } size_t get_mark_count() const { return marksm.size(); } double get_mark(size_t i) const { return marksm.at(i); } void set_name(const std::string& s) { namem = s; } void set_mark_count(size_t c) { marksm.resize(c); } void set_mark(size_t i, double m) { marksm.at(i) = m; } private: std::string namem; std::vector<double> marksm; }; #endif // MARKRECORDV1_H
#include <iostream> #include "MarkRecordV1.h" void display(const MarkRecordV1& mr) { std::cout << mr.get_name() << " s marks are"; for (size_t i = 0; i < mr.get_mark_count(); i++) std::cout << << mr.get_mark(i); std::cout << std::endl; } int main() { MarkRecordV1 a; a.set_name("alice"); a.set_mark_count(4); a.set_mark(0, 9.5); a.set_mark(1, 8.5); MarkRecordV1 b = a; b.set_name("bob"); b.set_mark(1, 7.5); display(a); display(b); return 0; }
ENCM 339 Fall 2016 Slide Set 14 slide 31/35 The program on the previous slide works because C++ can generate what are called compiler-synthesized member functions. Let s make notes about significant events in the execution of the program, describing the roles of compiler-synthesized member functions. What is the program output?
ENCM 339 Fall 2016 Slide Set 14 slide 32/35 Outline of Slide Set 14 Syntax for copy constructor and copy assignment operator functions The goal of a copy constructor The copy assignment operator The Law of the Big 3 Class objects within class objects Summary of rules for synthesized member functions
ENCM 339 Fall 2016 Slide Set 14 slide 33/35 Summary of rules for synthesized member functions Four kinds are possible: (1) default constructor (2) copy constructor (3) copy assignment operator (4) destructor (1) is synthesized only if the class does not explicitly define any constructors. (2) is synthesized if there is no explicit definition of a copy constructor, and at least one of the member variables is of a class that has a copy constructor. Similar rules apply for (3) and (4). The rules get more complicated when inheritance (not an ENCM 339 topic) is involved.
ENCM 339 Fall 2016 Slide Set 14 slide 34/35 class MarkRecordV2 { public: MarkRecordV2(const std::string& s) : namem(s), marksm() { } same set and get functions as before private: std::string namem; std::vector<double> marksm; }; Why doesn t this class have a default constructor? How could we add a default constructor that initializes both the string and the vector as empty? (There are many correct ways to do this; we ll just do one.)
ENCM 339 Fall 2016 Slide Set 14 slide 35/35 class MarkRecordV3 { public: same set and get functions as before set and get functions for ID number private: std::string namem; int idm; std::vector<double> marksm; }; Why is letting the compiler synthesize a default constructor a poor idea for this class?