1)
This exercise considers dynamic polymorphism in C.
You need to write low level code
(functions createDog
,
animalPrintGreeting
and
animalPrintMenu
)
which would enable correct execution
of the following test function.
void testAnimals(void){ struct Animal* p1=createDog("Hamlet"); struct Animal* p2=createCat("Ophelia"); struct Animal* p3=createDog("Polonius"); animalPrintGreeting(p1); animalPrintGreeting(p2); animalPrintGreeting(p3); animalPrintMenu(p1); animalPrintMenu(p2); animalPrintMenu(p3); free(p1); free(p2); free(p3); }
The test function testAnimals
should generate the following output.
Hamlet greets: howl! Ophelia greets: meow! Polonius greets: howl! Hamlet likes boiled beef Ophelia likes canned tuna Polonius likes boiled beef
Assume that the functions which describe the behaviour of particular animals are defined as follows.
char const* dogGreet(void){ return "howl!"; } char const* dogMenu(void){ return "boiled beef"; } char const* catGreet(void){ return "meow!"; } char const* catMenu(void){ return "canned tuna"; }
Note that the behaviours of
animalPrintGreeting
and
animalPrintMenu
correspond to the kind of the supplied object.
While designing your solution, please consider
the following instructions.
dogGreet
, catGreet
etc.
A suitable data type for storing
elements of these tables could be declared as:
typedef char const* (*PTRFUN)();
You shoud also define functions
for initializing these two tables.
Animal
containing
i) a pointer to the table of functions
which define the behaviour
of the corresponding concrete type, and
ii) a pointer to a char array
which contains the pet name.
Animal
but we usually prefer to have a pointer to a separate table
in order to ensure that objects of the same type
exhibit consistent behaviour as well as to
reduce the memory footprint of the concrete objects.
animalPrintGreeting
and
animalPrintMenu
which generate their output
by invoking the appropriate elements of the
function table of the supplied objects.
constructDog
and
constructCat
which receive
i) a pointer to the memory buffer
in which the new object is to be created, and
ii) a pointer to a string with the pet's name.
The functions should initialize an object
of the appropriate particular type
(dog and cat, respectively)
at the provided memory address.
createDog
and
createCat
which
first allocate the required memory buffer,
and then initialize the object by invoking
constructDog
or
constructCat
.
Please note that the declaration PTRFUN pfun;
in C (but not in C++!) defines a pointer to
a function with unspecified arguments.
Consequently, pfun
may point
to any function returning char const*
(details).
While using the pointer pfun
we must pay attention that count and types
of supplied arguments must match
the definition of the function to which our pointer points at
(otherwise, the program behaviour will be undefined).
Your solution must ensure that the memory footprint
of the created objects does not depend
on the number of virtual methods.
In other words, adding a new virtual method
should not increase sizeof(struct Animal)
.
Show that concrete objects can be created
both on the heap and ond the stack
(details).
You can allocate memory on the stack by instantiating
a local variable or a local char array.
Conversely, you can allocate on the heap
by invoking malloc
.
Write a function to create n dogs in a contiguous memory buffer, where n is the argument of the function (e.g. for a sled competition). Show how we can do this with one call to malloc and the required number of calls to constructDog.
After completing the exercise, establish the relation with the terminology of the object-oriented languages. Which elements of your solution correspond to the data members, methods, virtual methods, constructors and virtual tables?
If you are fascinated by object-oriented programming in C and want to learn more about it - have a look at the following book. However, before you make the final decision to switch from C++ to C, we recommend considering exceptions, templates, STLs, and new features offered by the 2011 and 2014 standards.
2)
This exercise considers the memory price of dynamic polymorphism.
The exercise shall be carried out in C++,
but similar conclusions would hold in other languages as well.
Consider the two classes CoolClass
and
PlainOldClass
as follows.
class CoolClass{ public: virtual void set(int x){x_=x;}; virtual int get(){return x_;}; private: int x_; }; class PlainOldClass{ public: void set(int x){x_=x;}; int get(){return x_;}; private: int x_; };Check the memory requirements of the two types by printing
sizeof(PlainOldClass)
and
sizeof(CoolClass)
.
Explain the obtained results.
3)
This exercise considers the time price of dynamic polymorphism.
The exercise shall be carried out in C++,
but similar conclusions would hold in other languages as well.
Consider the following modified version of CoolClass
and the following main program.
class Base{ public: //if in doubt, google "pure virtual" virtual void set(int x)=0; virtual int get()=0; }; class CoolClass: public Base{ public: virtual void set(int x){x_=x;}; virtual int get(){return x_;}; private: int x_; }; int main(){ PlainOldClass* poc=new PlainOldClass; Base* pb=new CoolClass; poc->set(42); pb->set(42); }Place the code above into a file named
mytest.cpp
,
and add the definition of the class PlainOldClass
shown in the previous exercise.
Compile the file by instructing the compiler
to skip optimizations and to produce the assembly code
(for instance, you might invoke gcc with
g++ -O0 -S -masm=intel mytest.cpp
).
Now check the way how the compiler implemented calls to
Base::set
and PlainOldData::set
.
A neat way to complete that would be
to search the constant 42 within the file
with the generated assembly code
(in the proposed example that file would be mytest.s
).
Explain the difference between the implementations of these two calls.
Which of the two implementations contains less instructions?
In which of the two cases would an optimizing compiler
be able to generate the code without using
the instruction CALL
,
that is by inlining the method body into the calling code?
Extra assignment for the patient:
try to locate the assembly code which defines
and initializes the virtual table
for the class CoolClass
.
Note that the tool c++filt
may be helpful for decoding decorated identifiers.
4) This exercise considers the particular behaviour of the polymorphic calls during the object construction. Try to explain the program output by analyzing the compiled machine code.
#include <stdio.h> class Base{ public: Base() { myMethod(); } virtual void myVirtualMethod() { printf("I am Base::myVirtualMethod!\n"); } void myMethod() { printf("The virtual method says: "); myVirtualMethod(); } }; class Derived: public Base{ public: Derived(): Base() { myMethod(); } virtual void myVirtualMethod() { printf("I am Derived::myVirtualMethod!\n"); } }; int main(){ Derived* pd=new Derived(); pd->myMethod(); }
5. Consider a component which is supposed to generate an appropriate sequence of integral numbers, and to report the 10th, 20th, ..., and the 90th percentiles of the corresponding distribution. The component should support the generation of integers in all of the following ways:
The component should be designed in order to allow inclusion of further ways to generate integers and calculating percentiles without changing the component itself.
Design a solution to the problem in concordance with the Strategy design pattern, and demonstrate its functionality by a suitable test driver. Discuss the properties of your design and compare it to an alternative solution which would be obtained according to the Template method design pattern.
6. Consider a class which is supposed to model a database connection by the following interface.
class MyDBase{ public: struct Param{ std::string table; std::string column; std::string key; }; virtual int query(const Param& p)=0; };The method
query
returns zero when successful
and otherwise the negative error code.
The objective of the exercise is to design decorators which are able to extend any given MyDBase object with the following additional functionality.
Test the developed decorators in combination
with a mock
implementation of the MyDBase
interface,
and demonstrate the possibility to remove decorators.
Discuss the properties of the developed design.