It seems that an almost obligatory and very important part of the recruitment process is "the test." "The test" can provide information both for the interviewer and the candidate. The interviewer is provided with a means to test the candidate's practical know-how and particular programming language understanding; the candidate can get an indication of the technologies used for the job and the level of proficiency the company expects and even decide if he still wants (and is ready for) the job in question.
I've had my fair share of interviews, more or less successful, and I would like to share with you my experience regarding some questions I had to face. I also asked for feedback from three of the top rated TopCoder members: bmerry, kyky and sql_lall , who were kind enough to correct me where I was not accurate (or plain wrong) and suggest some other questions (that I tried to answer to my best knowledge). Of course every question might have alternative answers that I did not manage to cover, but I tried to maintain the dynamics of an interview and to let you also dwell on them (certainly I'm not the man that knows all the answers). So, pencils up everyone and "let's identify potential C++ programmers or C++ programmers with potential."
// a C struct struct my_struct { int someInt; char* someString; }; // you declare a variable of type my_struct in C struct my_struct someStructure; // in C you have to typedef the name to easily // declare the variable typedef my_struct MyStruct; MyStruct someOtherStuct; // a C++ struct struct MyCppStruct { int someInt; char* someString; }; // you declare a variable of type MyCppStruct in C++ MyCppStruct someCppStruct; // as you can see the name is automatically typedefed
// myInt is a constant (read-only) integer const int myInt = 26; // same as the above (just to illustrate const is // right and also left associative) int const myInt = 26; // a pointer to a constant instance of custom // type MyClass const MyClass* myObject = new MyObject(); // a constant pointer to an instance of custom // type MyClass MyClass* const myObject = new MyObject(); // myInt is a constant pointer to a constant integer const int someInt = 26; const int* const myInt = &someInt;
class B : public A { };
class B : private A { }; or class B : A { };
class B : public A {}; B "is a" A but class B : private A {};means B "is implemented in terms of" A.
A* aPointer = new B();
Private inheritance, on the other hand, class B : private A {};, does not create subtypes making the base type inaccessible and is a form of object composition. The following illustrates that: class A { public: A(); ~A(); void doSomething(); }; void A :: doSomething() { } class B : private A { public: B(); ~B(); }; B* beePointer = new B(); // ERROR! compiler complains that the // method is not accessible beePointer->doSomething(); // ERROR! compiler complains that the // conversion from B* to A* exists but // is not accessible A* aPointer = new B(); // ! for the following two the standard // stipulates the behavior as undefined; // the compiler should generate an error at least // for the first one saying that B is not a // polymorphic type A* aPointer2 = dynamic_cast<A*>(beePointer); A* aPointer3 = reinterpret_cast<A*>(beePointer);
class Friendly; // class that will be the friend class Friend { public: void doTheDew(Friendly& obj); }; class Friendly { // friends: class and function; may appear // anywhere but it's // better to group them toghether; // the default private access specifier does // not affect friends friend class Friend; friend void friendAction(Friendly& obj); public: Friendly(){ }; ~Friendly(){ }; private: int friendlyInt; }; // the methods in this class can access // private members of the class that declared // to accept this one as a friend void Friend :: doTheDew(Friendly& obj) { obj.friendlyInt = 1; } // notice how the friend function is defined // as any regular function void friendAction(Friendly& obj) { // access the private member if(1 == obj.friendlyInt) { obj.friendlyInt++; } else { obj.friendlyInt = 1; } }
class MyFancyClass { public: // default constructor MyFancyClass(); // copy constructor MyFancyClass(const MyFancyClass&); // destructor ~MyFancyClass(); // assignment operator MyFancyClass& operator=(const MyFancyClass&); };
inline SomeClass* SomeClass::operator&() { return this; }
Thanks to bmerry for making me doubt what seemed the obvious. I found out the following:
From the ISO C++ standard:
Clause 13.5/6 [over.oper] states that operator =, (unary) & and , (comma) have a predefined meaning for each type. Clause 5.3.1/2 [expr.unary.op] describes the meaning of the address-of operator. No special provisions are mode for class-type objects (unlike in the description of the assignment expression). Clause 12/1 [special] lists all the special member functions, and states that these will be implicitly declared if needed. The address-of operator is not in the list.
From Stroustrup's The C++ Programming Language - Special 3rd Edition:
"Because of historical accident, the operators = (assignment), & (address-of), and , (sequencing) have predefined meanings when applied to class objects. These predefined meanings can be made inaccessible to general users by making them private:... Alternatively, they can be given new meanings by suitable definitions."
From the second edition of Meyer's Effective C++:
"A class declaring no operator& function(s) does NOT have them implicitly declared. Rather, compilers use the built-in address-of operator whenever "&" is applied to an object of that type. This behavior, in turn, is technically not an application of a global operator& function. Rather, it is a use of a built-in operator." In the errata http://www.aristeia.com/BookErrata/ec++2e-errata_frames.html
// a simple base class just for illustration purposes class SimpleBase { public: SimpleBase(string&); ~SimpleBase(); private: string& m_name; }; // example of initializer list with a call to the // data member constructor SimpleBase :: SimpleBase(string& name) : m_name(name) { } // a class publicly derived from SimpleBase just for // illustration purposes class MoreComplex : public SimpleBase { public: MoreComplex(string&, vector<int>*, long); ~MoreComplex(); private: vector<int>* m_data; const long m_counter; }; // example of initializer list with calls to the base // class constructor and data member constructor; // you can see that built-in types can also be // constructed here MoreComplex :: MoreComplex(string &name, vector<int>* someData, long counter) : SimpleBase(name), m_data(someData), m_counter(counter) { }
SimpleBase :: SimpleBase(string &name) { m_name = name; }
The above will generate a call to the default string constructor to construct the class member m_name and then the assignment operator of the string class to assign the name argument to the m_name member. So you will end up with two calls before the data member m_name is fully constructed and initialized.
SimpleBase :: SimpleBase(string &name) : m_name(name) { }
The above will only generate a single call, which is to the copy constructor of the string class, thus being more efficient.
That's it for the first part of this installment. Stay tuned for the second one, as we're going to go deeper into the language features. Good luck on those interviews!
In the second part of this installment we'll tackle some questions regarding more advanced features of the language (the experienced C++ programmers will consider some of these more on the basic side). So let's get to it and work on the second part of this "interview".
class Shape { public: ... //a shape can be drawn in many ways virtual void draw(){ }; };"A virtual function must be defined for the class in which it is first declared ..." [Stroustrup]. The redefinition of a virtual function in a derived class is called overriding (complete rewrite) or augmentation (rewrite but with a call to the base class function)
class Rectangle : public Shape { public: ... void draw() { }; }; class Square : public Rectangle { public: ... void draw() { }; }; ... Shape* theShape = new Square(); // with the help of virtual functions // a Square will be drawn and not // a Rectangle or any other Shape theShape->draw();
#include <vector> #include <iostream> using namespace std; class Base { public: Base(const char* name); // warning! the destructor should be virtual ~Base(); virtual void doStuff(); private: const char* m_name; }; Base :: Base(const char* name) : m_name(name) { } Base :: ~Base() { } void Base :: doStuff() { cout << "Doing stuff in Base" << endl; } class Derived : public Base { public: Derived(const char* name); ~Derived(); virtual void doStuff(); private: vector<int>* m_charCodes; }; Derived :: Derived(const char* name) : Base(name) { m_charCodes = new vector<int>; } Derived :: ~Derived() { delete m_charCodes; } void Derived :: doStuff() { cout << "Doing stuff in Derived" << endl; } int main(int argc, char* argv[]) { // assign the derived class object pointer to // the base class pointer char* theName = "Some fancy name"; Base* b = new Derived(theName); // do some computations and then delete the // pointer delete b; return 0; }What will happen in our rather lengthy example? Everything seems OK and most of the available C++ compilers will not complain about anything (*). Nevertheless there is something pretty wrong here. The C++ standard is clear on this topic: when you want to delete a derived class object through a base class pointer and the destructor of the base class is not virtual the result is undefined. That means you're on your own from there and the compiler won't help you! What is the most often behavior in such situations is that the derived class' destructor is never called and parts of your derived object are left undestroyed. In the example above you will leave behind a memory leak, the m_charCodes member will not be destroyed because the destructor ~Derived() will not be called
class MySillyAbstract { public: // just declared not defined virtual void beSilly() = 0; };A derivation from an abstract class must implement all the pure virtuals, otherwise it transforms itself into an abstract class
class IOInterface { public: virtual int open(int opt) = 0; virtual int close(int opt) = 0; virtual int read(char* p, int n) = 0; virtual int write(const char* p, int n) = 0; };[adapted after an example found in Stroustup The C++ Programming Language 3rd Edition]
string* array1 = static_cast<string*>(malloc(10 * sizeof(string))); free(array1);array1 in the above example points to enough memory to hold 10 strings but no objects have been constructed and there's no easy and clean (proper) way from OO point of view to initialize them (see the question about placement new - in most day to day programming tasks there's no need to use such techniques). The call to free() deallocates the memory but does not destroy the objects (supposing you managed to initialize them).
string* array2 = new string[10]; delete[] array2;on the other hand array2 points to 10 fully constructed objects (they have not been properly initialized but they are constructed), because "new"allocates memory and also calls the string default constructor for each object. The call to the delete operator deallocates the memory and also destroys the objects
// supposing a "buffer" of memory large enough for // the object we want to construct was // previously allocated using malloc MyClass* myObject = new (buffer) MyClass(string& name); // !!ERROR delete myObject; // the correct way is myObject->~MyClass(); // then the "buffer" must also be properly // deallocated free(buffer);
// Product class Page { }; // ConcreteProduct class SkillsPage : public Page { }; // ConcreteProduct class ExperiencePage : public Page { }; // ConcreteProduct class IntroductionPage : public Page { }; // ConcreteProduct class TableOfContentsPage : public Page { }; // Creator class Document { // Constructor calls abstract Factory method public: Document(); // Factory Method virtual void CreatePages() { }; protected: std::list<Page*> thePageList; }; Document :: Document() { CreatePages(); }; // ConcreteCreator class Resume : public Document { public: // Factory Method implementation void CreatePages(); }; // Factory Method implementation void Resume :: CreatePages() { thePageList.push_back(new SkillsPage()); thePageList.push_back(new ExperiencePage()); } // ConcreteCreator class Report : public Document { public: // Factory Method implementation void CreatePages(); }; // Factory Method implementation void Report :: CreatePages() { thePageList.push_back(new TableOfContentsPage()); thePageList.push_back(new IntroductionPage()); } int main(int argc, char* argv[]) { // Note: constructors call Factory Method vector<Document*> documents(2); documents[0] = new Resume(); documents[1] = new Report(); return 0; }
// this is a hypothetic LogFile class using an // hypothetic File class just for the illustration // of the technique class LogFile { public: LogFile(const char*); ~LogFile(); void write(const char*); private: File* m_file; }; LogFile :: LogFile(const char* fileName) : // ! acquisition and initialization m_file(OpenFile(fileName)) { if(NULL == m_file) { throw FailedOpenException(); } } LogFile :: ~LogFile() { // ! release and uninitialization CloseFile(m_file); } void LogFile :: write(const char* logLine) { WriteFile(m_file, logLine); } // a hypothetical usage example void SomeClass :: someMethod() { LogFile log("log.tx"); log.write("I've been logged!"); // !exceptions can be thrown without // worrying about closing the log file // or leaking the file resource if(...) { throw SomeException(); } }
That was it, folks! I hope that even if those questions did not pose any challenges, you still had fun doing/reading this quiz and refreshing your memory on some aspects of the C++ language. Good luck on those interviews!
(*) bmerry suggested that my claim is not accurate but I've tested the example on Windows XP: Visual Studio 2005 Professional Edition (the evaluation one that you can get from the Microsoft site ) did not warn, not even after setting the warnings level to Level 4 (Level 3 is the default one); Mingw compiler based on GCC (that comes with the Bloodshed DevCpp version 4.9.9.2) also did not warn (the compiler settings from within the IDE are minimalist; tried to pass -pedantic and -Wextra to the compiler command line but still no success); Digital Mars C++ compiler (dmc) also did not warn with all warnings turned on; Code Warrior Professional Edition 9 does not warn also (this is pretty old, but Metrowerks compilers were renowned for the robustness and standard conformance). So, unless you start digging through the documentation of those compilers to find that right command line switch or start writing the right code, you're in the harms way at least with the "out of the box" installations of these compilers.
(**) The compiler does all the magic: first, for each class that contains virtual functions (base and derived), the compiler creates a static table called theVTABLE. Each virtual function will have a corresponding entry in that table (a function pointer); for the derived classes the entries will contain the overridden virtual functions' pointers. For each base class (it's not static, each object will have it) the compiler adds a hidden pointer called the VPTR, that will be initialized to point to the beginning of the VTABLE - in the derived classes the (same) VPTR will be initialized to point to the beginning of the derived class' VTABLE. So when "you make a virtual function call through a base class pointer (that is, when you make a polymorphic call), the compiler quietly inserts code to fetch theVPTR and look up the function address in the VTABLE, thus calling the correct function". This might seem overly complicated but on a typical machine it does not take much space and it's very, very fast as a smart man said once "fetch, fetch call".
(***) For that and other fine C++ gems go to Stroustrup.