CSE 428: Solutions of exercises on Object Oriented programming and C++


The superscript "(d)" stands for "difficult". Exercises similar to those marked with "(d)" might appear in candidacy exams, but not in the standard exams of CSE 428.


  1. Consider the following class declarations in C++:
       class C { 
           protected: int x; 
           public: void f(){...}; 
       }; 
    
       class C1: public C { 
           protected: int x1; 
           public: void h(C *obj){...}; 
       }; 
    
       class C2: public C1 { 
           public: int x2; 
       }; 
    
       class C3: public C { 
           public: f(){...}; 
       }; 
    
    1. Draw the class hierarchy
    2. Assume that main contains the following declaration:
         C1 obj1; 
      
      For each of the following expressions, say whether it is allowed in the code of main or not (they can be forbidden either because they violate the class definition or the protection mechanism)
         obj1.x , obj1.f() , obj1.x1 , obj1.x2 
      
    3. (d) Assume that the body of C1::h contains the following declarations
         C2 *obj2;
         C3 *obj3;
      
      For each of the following expressions, say whether it is allowed in the body of C1::h or not
         obj->x , obj2->x , obj3->x 
      
    4. Assume that the body of C1::h contains the following statement
         obj->f();
      
      Assume that we call C1::h with a parameter (pointing to an object) of class C3. What is the method f executed, C::f() or C3::f()? And what would be the method executed if f were declared virtual in C?

    Solution

    1. The class hierarchy is represented by the following graph:
            C
           / \
          /   \
         C1   C3
         |
         |
         C2
      
      • obj1.x is not allowed in main, because x is a protected field of C, hence it cannot be accesses externally.
      • obj1.f() is allowed, because f() is a public method inherited by C1.
      • obj1.x1 is not allowed in main, because x1 is a protected field.
      • obj1.x2 is not allowed in main, because x2 is not a member of C1.
      • obj->x is not allowed in C1::h, because x is a protected field of C. The rationale is that obj could be replaced, at run time, by an object of a subclass not in the same branch of C1.
      • obj2->x in C1::h is allowed. This might look a bit strange, especially in contrast to the previous question, but is what the "Annotated C++ Reference Manual" (by Ellis and Stroustrup) prescribes.
      • The idea is that protected fields and methods should be applicable (in C1) to objects which are guarranteed to belong to classes in the same branch of C1.
      • obj3->x is not allowed in C1::h, because x is a protected field of C, inherited as protected by C3.
    2. The method executed is C::f(). If C::f() were decleared virtual, then the method executed would be C3::f().

    Below is the result of compilation and execution using g++

    // FILE Exercise_1.C
    
    #include < iostream.h >
    
    class C { 
    protected: 
       int x; 
    public: 
       void f(){ cout << "executes C::f()" << '\n'; }
       virtual void g(){ cout << "executes C::g()" << '\n'; }
    }; 
    
    class C1: public C { 
    protected: 
       int x1; 
    public: 
       void h(C *); 
    }; 
    
    class C2: public C1 { 
    public: 
       int x2; 
    }; 
    
    class C3: public C { 
    protected: 
       int x3; 
    public: 
       void f(){ cout << "executes C3::f()" << '\n'; }
       void g(){ cout << "executes C3::g()" << '\n'; }
    }; 
    
    void C1::h(C *obj){
       C2 *obj2 = new C2;
       C3 *obj3 = new C3;
       //obj->x = 0;
       obj2->x = 2;
       //obj3->x = 3;
       cout << "method obj->f() from C1::h: "; obj->f();
       cout << "method obj->g() from C1::h: "; obj->g();
       cout << "method obj3->f() from C1::h: "; obj3->f();
    } 
    
    void main(){     
       C1 obj1; 
       //obj1.x = 0;
       cout << "method obj1.f() from main: "; obj1.f(); 
       //obj1.x1 = 1;
       //obj1.x2 = 2;
       C3 o3;
       obj1.h(&o3);
    }
    
    /* Errors (before commenting the lines producing errors)
    % g++ Exercise_1.C
    Exercise_1.C: In method `void C1::h(class C *)':
    Exercise_1.C:32: member `x' is a protected member of class `C'
    Exercise_1.C:34: member `x' is protected
    Exercise_1.C: In function `int main(...)':
    Exercise_1.C:41: member `x' is protected
    Exercise_1.C:43: member `x1' is a protected member of class `C1'
    Exercise_1.C:44: `class C1' has no member named `x2'
    */
    
    /* Result of execution 
    % a.out
    method obj1.f() from main: executes C::f()
    method obj->f() from C1::h: executes C::f()
    method obj->g() from C1::h: executes C3::g()
    method obj3->f() from C1::h: executes C3::f()
    */
    

  2. Consider the following template definition in C++:
       template < class T > class cell { 
           protected: 
               T info; 
           public: 
               void set(T x){ info = x; } 
               T get() { return info; } 
       }; 
    
    Define the subclass colored_cell by extending the class cell with: Choosing the right signature, types and parameters is part of the exercise.

    Solution

       template < class T > class colored_cell: public cell< T >{ 
           protected: 
               char color; 
           public: 
               void set_color(char c){ color = c; } 
               char get_color() { return color; } 
               T get() { if (color != 'w') return info; else return 0; }
       }; 
    

  3. Consider the demand driven version of the Erathostenes' Sieve (Lecture 12). Write a destructor so that, when the main program terminates, the whole structure of filters is deallocated.

    Solution

    It is sufficient to add to the class Item the following destructor method:
       ~Item(){ if (source!=NULL) delete source; } 
    

  4. Consider the class List defined at page 242 of Sethi's book, and consider the following main program:
       main(){
          List *l = new List();
          l->push(1);
          l->push(2);
          delete l;
       }
    
    This program leaves memory leaks. Why? Change the definition of ~List so that, when it is invoked, the destructor deallocates the whole structure.

    Solution

    After delete l we have a memory leak, because the destructor ~List does not deallocate the auxiliary cell pointed by rear. In order to fix this problem, it is sufficient to add delete rear to the destructor method:
       ~List(){ 
          while (!empty()) pop(); 
          delete rear; 
       }
    

  5. Consider again the class List defined at page 242 of Sethi's book, with the new method ~List defined in previous exercise. For each of the following programs main, say whether it leaves memory leaks or not.
    1.    main(){
            List l;
            l.push(1);
            l.push(2);
      }
      
    2.    main(){
            List *l = new List();
            l->push(1);
            l->push(2);
            List *k = l;
            delete k;
      }
      
    3.    main(){
            printall(listall(100));
      }
      
      where printall and listall are defined as follow:
         List *listall(int n){
            List *l = new List();
            while (n>0) l->push(n--);
            return l;
         }
      
         void printall(List *l){
            while (!(l->empty())) cout << l->pop();
         }
      

    Solution

    1. No memory leaks. The destructor is automatically invoked when l goes out of scope.
    2. No memory leaks. l and k point to the same structure, which gets deallocated when ~List() is invoked, as a consequence of delete k.
    3. We have two memory leaks, because the call of listall(100) creates a list (in the heap) whose pointer becomes the actual parameter of the call of printall. The execution of printall deallocates (via the call of pop() all the elements of the list, but it does not deallocate the list itself and the auxiliary cell pointed by rear. After the execution of printall the parameter goes out of scope, and the list is not reachable anymore.