Solutions for final exam study questions, Comp 272, Dordal > 1. Write member function equals(Shape* s2) for the Group class. > Explain why it has to be virtual. The following example may help, but your Group version should be sure to do a "deep" comparison. > bool Polygon::equals(Shape * s2) { > if (typeid(s2)!=typeid(Polygon)) return false; > Polygon * p2 = (Polygon *) s2; // cast! > if (*this != *p2) return false; > else return true; > } > Explain why typeid() is used here. If s2 points to another kind of shape altogether, there's no point doing further comparison and no basis for doing further comparison either. The cast of s2 to a Polygon* in the following line would be incorrect. bool Group::equals(Shape * s2) { if (typeid(s2) != typeid(Group)) return false; Group * g2 = (Group*) s2; // so far the same, but... if (_shapelist.size() != g2->_shapelist.size()) return false; for (int i=0; i< _shapelist.size(); i++) { if ( ! _shapelist[i]->equals(g2->_shapelist[i])) return false; } return true; // if no discrepancies found, they must be equal! } It does not suffice just to compare for == the two groups pointed to, "if (*this == *g2)". That would just do a byte-by-byte comparison of the two _shapelists (unless operator== had been defined to be a "deep compare" operation). This would mean that they would be equal only if each pair of corresponding pointers were equal, that is, the subshapes of the two groups were actually the identical instances of shape objects and not just two separate instances that happened to be equal. > 2. State what has to be provided for a Shape::clone() operation > that returns a Shape* that represents a deep copy of the original. > Assume that Group is one of the classes derived from Shape, > and is the only one with embedded Shape pointers. > Give the implementation of Group::clone(). Class Shape would provide an abstract clone(): Shape * clone() const = 0; Then, non-pointer subclasses would just use the copy ctor to create a new shape in their image: Class Polygon : public Shape { ... Shape * clone() const { return new Polygon(*this); } ... }; The default byte-copy copy ctor for Polygon would suffice. For group, though, with pointers, we would either have to provide a copy ctor for Group that cloned each pointer, or we would have to make clone() call itself recursively to clone each subshape. Here is the latter approach: Shape * clone() const { Group * g = new Group; // initially empty for (int i=0; i<_shapelist.size(); i++) { g->add(clone(_shapelist[i])); // clone each shape, add to g } return g; }; With a little more care we could have allocated *g's _shapelist initially to be the same size as *this._shapelist, but that takes more coordination. > 3. Write declarations for member functions of a Date class as > described in Horstmann for conversion > string => Date. > Do not write the function *definition*. class Date { ... Date(string s); // ctor; defines string=>Date conversion // while we're at it, although this won't be on the exam, // here's how to do a conversion in the *other* direction: operator string(); // defines Date=>string conversion ... }; > 4. Assume you have a List class like that on p. 369 of Horstmann. > Give a full declaration and implementation of operator[] so that > L[n] returns a reference to the nth element (starting at 0) of list L. > Explain the difference between returning a reference to the nth > element, and returning the nth element's value. Just returning the nth element's value returns a copy of it; modifying the copy will not alter the original. Returning a reference means that the implementation in effect returns a pointer to the original location; if you just use it as a value then you would get the value, but if you assign to it, then you are modifying the original location. Horstmann's class was a list of ints, but I will use T here as the type of the list data. I will partly ignore the possibility we run off the end of the list! T & List::operator[](int i) { Link * p = List._head; if (i>List._length) return 0; // a botch, if 0 isn't of type T while(i>0) { i--; p = p->_next; } return p->_info; // of type T, automatically converted to T&. } > 5. Suppose we have a Group G2 (from the Shape class) and then execute > Group G = G2; > G.move(100,0); // move entire group 100 units to the right > G.plot(g); > G2.plot(g); > How will the output of the two plot() calls compare if the copy > constructor for Group does a > (a) shallow copy? > (b) deep copy? For the deep copy, we will get two copies of the image, spaced 100 units apart. For the shallow copy the two images will be superimposed. When we called G.move(100,0), that modified each object pointed to by G to be shifted 100 units to the right. Since G and G2 share identical pointers, this means that all the shapes the original G2 pointed to are moved. > 6. For classes with dynamically allocated memory, why do you need > operator= separately from the copy constructor? Both allocate new memory, *but* only operator= also deallocates (or otherwise has to manage) existing memory. > 7. Write operator+=(vector v1, vector v2) so that the > items in v2 are copied to the end of v1. We make this return vector&, so that the += operations can be chained. If you don't need to do that, having this return void sufficies. We also allocate space all at once; the alternative would be v2.size() many calls to v1.push_back() which would also work. Note that this is *not* being defined here as a member of class vector (which we don't have access to, anyway). vector& operator+= (vector v1, const vector v2) { int oldsize = v1.size(); v1.resize(oldsize + v2.size()); // reallocate space all at once for (int i=0; i Write operator+=(vector v1, vector v2> so that the > corresponding components of v2 are added to v1 > You may assume that v1 and v2 have the same size(). vector& operator+= (vector v1, const vector v2) { for (int i=0; i