C++ Questions& Answers / Interview Practice Questions

Question: What is the output of the following code?

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
#include 
#include 
#include 

struct g
{
 g():n(0){}
 int operator()() { return n++; }
 int n;
};

int main()
{
 int a[10];
 std::generate(a, a+10, g());
 std::copy(a, a+10, std::ostream_iterator(std::cout, " "));
}


Answer: 0 1 2 3 4 5 6 7 8 9

Why? The function main() uses the generate algorithm to initialise the int array using functor g. This functor will be called for every element within the array, with the result of the call use to initialise that element. Once initialise, the whole array is copied to the standard output stream using the copy algorithm. Most of STL makes good use of functors and there are some neat reusable generic algorithms, which can be used to make code simple and easier to maintain.

          

Question: What is the value of bSame and why?

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
#include 

struct S
{
   float f;
   char c;
   int i;
};

int main()
{
   S s1 = { 1.1f, 'a', 99 };
   S s2 = { 1.1f, 'a', 99 };

   bool bSame = memcmp(&s1, &s2, sizeof(S)) == 0;
}


Answer: The value of bSame is undefined. The reason is that compilers are allowed to put padding into struct and class types for data alignment to preserve word boundary alignment and for efficiency reasons they are not obliged to initialize this padding to any specific value. This being the case the result will be compiler specific and, therefore, undefined. This code is not portable and although it might work on your compiler or even your specific version of the build (for example, in a debug build in Visual Studio the compiler does null out structures to facilitate debugging) there is no guarantee this will work on other platforms or other compilers.

You can mitigate this by using memset to nullify the memory first but this should be avoided on non POD types as it can have unexpected side effects (such as obliterating the v-table of a class with virtual functions). In short, the only safe way to compare structures is to perform a member by member comparison.

          

Question: The following is perfectly valid C++. How can this be given that main specified a return type of int yet no return statement is to be found?

1:
2:
int main(){}


Answer: In C++ the return is an optional statement in (and only in) the main function, with 0 being implicitly returned if the return statement is omitted. This is a special case that only applies to the main function.

          

Question: Two questions related to character strings...

a) What is wrong, if anything, with the following code?

1:
2:
char * p = "foobar";


b) How do these 2 lines of code differ?

1:
2:
3:
char const * p = "foobar";
char const s[] = "foobar";


Answer: Two answers related to character strings...

a) Since a literal string decays to a pointer type of char const * and NOT char * this is not strictly speaking legal C++; however, to maintain backwards compatibility with C, compilers will allow this but they (should) produce a warning since this construct is now deprecated in C++ and will be removed from future versions of the standard.

b) Line one is a pointer to a literal string, which is immutable and attempting to modify the string will result in undefined behaviour. Line two is an array that is initialised with a string value, which means you are allowed to remove the const specifier and modify the array if you so wish (since you own the memory that represents the array).

          

Question: Is the following code safe and if not why?

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
struct Foo
{
   void func(){}
};

typedef void (Foo::*func_t)();

int main()
{
   func_t fp1 = func_t(&Foo::func);
   void * p = (void*) fp1;
   func_t fp2 = (func_t) p;

   Foo foo;
   (foo.*fp2)();
}


Answer: A pointer to a member is not a pointer to an object or a pointer to a function and the rules for conversions of such pointers do not apply to pointers to members. In particular, a pointer to a member cannot be converted to a void*.

NB. Also note that function pointers cannot be converted to void *.

More info: C++ FAQ Lite (member function pointers)
More info: C++ FAQ Lite (function pointers)

          

Question: What is the value of a and i after this code runs?

1:
2:
3:
4:
5:
int i = 0;
char a[2] = { char() };

a[i++] = ++i;


Answer: The behaviour of the code in undefined. A variable cannot be modified more than once in any expression unless the modification is punctuated with a sequence point. A sequence point (of which there are 6 in standard C++) guarantees that all the side effects of the previous expression are complete and no side effects from sub-sequence expression have been performed. Since i is modified and read twice in this expression the result is undefined.

          

Question: The following is a nice and simple way to get a non-const pointer to the internal buffer of a string, right?

1:
2:
3:
4:
string s("hallo,world");
char * p = &s[0];
p[1] = 'e';


Answer: No, the current C++ standard does not guarantee that the internal buffer of a string is contiguous. The only STL container to have this guarantee is a vector.

Question: This is a perfectly innocuous looking piece of code, but what hidden problem does it hide?

1:
2:
namespace { std::string const GLOBAL_VAL("some value"); }


Answer: It is a non-pod (non Plain Old Data) object used at file scope. Since any object with a non-trivial constructor can throw an exception and the C++ standard makes no guarantees that the STL contains won't, this code is unsafe. Were an exception to be thrown could not be caught and the application would crash. Incidentally, this problem also applies to static class members.

          

Question: What is the result of the following code?

1:
2:
3:
4:
5:
6:
7:
// Translation unit "foo.cpp"
int const FOO = 1;

// Translation unit "bar.cpp"
#include "foo.h" // declares int FOO
int const BAR = FOO


Answer: The result is undefined because translation units are not compiled in any predefined order. If bar.cpp is compiled before foo.cpp then the value of BAR will be undefined.

Can we resolve this? Well, yes! There are many ways to solve this issue but by far the simplest is to use the Mayer's Singleton pattern (named after Scott Mayer the acclaimed author of Effective C++). This relies on the fact that although we can't control the order of initialization of global objects we are guaranteed that if a global function is called all members of that function (including static members) are constructed during the lifetime of that function call and if the function then returns that static member, even if it is in a different translation unit, the object being referenced will be fully constructed. This is easier to understand with a simple code example

1:
2:
3:
4:
5:
6:
7:
// Translation unit "foo.cpp"
int const FOO() { static int const FOO_ = 1; return FOO_; }

// Translation unit "bar.cpp"
#include "foo.h" // declares int FOO()
int const BAR = FOO()


Of course this is a very simplistic example using an int, but this principle works with all types including user defined objects (class instances).

          

Question: Is the following a definition or a declaration?

1:
2:
Foo f(Bar());


Answer: It is possibly either a declaration of a function that takes type Bar and returns type Foo or it is a definition of f as a type Foo, which has a constructor that takes type Bar. The problem is the syntax for both is identical so to resolve this problem the C++ standard states that a compiler must prefer function declarations to object definitions where it is unable to make a distinction.

          

Question: Is this code safe?

1:
2:
3:
4:
5:
6:
typedef std::vector vec_t;
vec_t v(100);
vec_t::const_iterator i = v.begin() + 50;
v.push_back(5);
(*i) = 5;


Answer: No because the push-back potentially invalidates the iterator. Unless memory has already been reserved in the vector, the internal buffer may need to grow to accommodate the new value added. Since C++ has no equivilent to realloc a whole new buffer must be created, the old copied to the new and the old destroyed. Not only does this invalidate the iterator but it is incredibly inefficient. For this reason, you should always try to pre-size a vector before adding new things to it.

          

Question: Why won't the following code work (assuming the current C++ 2003 Standard) and how can it be fixed?

1:
2:
std::auto_ptr pInt = new int;


Answer: The auto_ptr constructor is defined as explicit so assignment constructor semantics won't work (explicit constructors are not considered). To make this work normal constructor semantics must be used.

1:
2:
std::auto_ptr pInt(new int);


          

Question: Is the auto_ptr in the following code going to prevent memory leaks when it goes out of scope?

1:
2:
std::auto_ptr pInt(new int[10]);


Answer: Maybe, maybe not; however, the code is not well-formed. The auto_ptr calls non-array delete on its managed memory and since an array of int types has been allocated using array-new the C++ standard defines the result of deleting using normal delete as undefined (even for intrinsic types).

          

Question: I should use auto_ptr if I want to store pointers to heap allocated objects in STL containers, right?

Answer: No no no :(

The auto_ptr has move semantics and not copy or reference semantics. This means, if you assign it to another auto_ptr the pointer it is managing transfers to the new auto_ptr and the old one is set to NULL. The basic problem is that as soon as you try to access any element in the container by value the ownership will transfer, leaving the auto_ptr in the container pointing to NULL and not the original heap allocated object. The C++ standard defines the behavior of using an auto_ptr in an STL container as undefined. If you need a good reference covering smart pointers, take a look at the shared_ptr, which is part of Boost.

          

Question: Are there any potential issues with this function?

1:
2:
3:
4:
5:
6:
std::string foo() throw()
{
 std::string s("hello world");
 return s;
}


Answer: Yes. The function declares it doesn't percolate any exceptions but all STL containers can throw exceptions on construction. The default behaviour in this case is for the C++ framework to call the unexpected function to handle this and the default behaviour of the unexpected function is to call terminate (can you guess what will happen next?).

          

Question: What is RVO and NRVO? What potential issues does NRVO introduce? Are there any special considerations for facilitating the compiler?

Answer: Return Value Optimization and Named Return Value Optimization.

RVO: An in-line constructed return value can be returned from a function by value directly into the memory space allocated to the object to which it will be returned. This is an optimization that ensures the compiler doesn't need to take a copy of a type being returned by value. The possible issue is that the copy constructor won't be called, so if your code relies on this you may get unexpected results.

1:
2:
3:
4:
5:
6:
// Example of RVO
Bar Foo()
{
 return Bar();
}


NRVO: A named value can be returned from a function by value directly into the memory  space allocated to the object to which it will be returned. This is an optimization that ensures the compiler doesn't need to take a copy of a type being returned by value. The possible issue is that the copy constructor won't be called, so if your code relies on this you may get unexpected results.

1:
2:
3:
4:
5:
6:
7:
// Example of NRVO
Bar Foo()
{
 Bar bar;
 return bar;
}


It is not always possible for a compiler to carry out NRVO, code must be written to facilitate it. This does vary from compiler to compiler but, for example, if there are multiple return paths you can be pretty sure NRVO will not take place.

More info: Return Value Optimization techniques implemented by C++ compilers

          

Question: Is there anything wrong with the 2 code snippets below?

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
// Snippet 1
struct Bar{};
Bar foo(){ return Bar(); }
Bar & b = foo();

// Snippet 2
struct Bar{};
void foo(Bar & b){  }

int main()
{
 foo(Bar());
}


Answer: Temporary types are always r-values (values ONLY allowed on the right hand sight of an assignment [e.g., lvalue = rvalue]), so that can only be assigned to const reference (NB. unfortunately, Visual Studio doesn't enforce this standards requirement). This is quite a frustration as there seems no logical reason for this, so the C++ Standards Committee is looking to provide a solution in the guide of a rvalue reference in the next ratified version of the C++ standard (C++0X).

          

Question: Explain what this code does?

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
template  struct A{};
template  struct B : A{};

template 


Answer: Function foo is a template function that takes 2 parameters, the first is a template template parameter (TTP), the second is a concrete type. The function instantiates a concrete type of the TTP using type T and returns this type by value.

          

Question: What is SFINAE?

Answer: Substitution Failure Is Not An Error. It refers to the fact that during template function overload resolution, invalid substitution of template parameters is not in itself an error, rather the compiler will just drop the candidate template from the possible candidates and as long as at least one candidate results in a valid substitution the program shall be considered well formed.

          

Question: What is the difference between the "Point of declaration" and the "Point of instantiation".

Answer: The POD is the point where the declaration of the template first comes into scope making the compiler aware that the template definition exists. The POI is the point where the template is instantiated, creating a concrete type from the declaration as found at the POI.

More info: Seperating C++ template declaration and implementation

          

Question: Without using a compiler, what is the value of st where noted by the comment?

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
struct Foo
{
   virtual size_t func(size_t st = 0) const = 0;
};

struct Bar : Foo
{
   virtual size_t func(size_t st = 999) const
   {
      return st;
   }
};

int main()
{
   Foo const & foo = Bar();
   size_t st = foo.func(); // What value does st have?
}


Answer: On return st will be assigned 0 and not, as one would intuitively expect, 999. The reason for this is because default parameters are always bound to the static and NOT dynamic type. This case be a very hard to track down source of defects, be very careful when using default parameters with virtual functions.

More info: Guru of the week: Overriding Virtual Functions

          

Question: What, if anything, is wrong with this code?

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
struct Bar
{
   template 
   void func() const { }

   template 
   static void sfunc() { }
};

template 
void Foo1(T const & t)
{
   t.func();
   T::sfunc();
}

template 
void Foo2(T const * pT)
{
   pT->func();
}

int main()
{
   Bar bar;
   Foo1(bar);
   Foo2(&bar);
}


Answer: The compiler can't figure out what t, T and pT are during the first pass template instantiation so you need to tell it that what follows is a template function. You do this using the ->template, .template and ::template operators.

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
struct Bar
{
   template 
   void func() const { }
 
   template 
   static void sfunc() { }
};
 
template 
void Foo1(T const & t)
{
   t.template func();     //<--- The compiler doesn't know what t is, so you need to tell it that it's a template type
   T::template sfunc();   //<--- The compiler doesn't know what T is, so you need to tell it that it's a template type
}
 
template 
void Foo2(T const * pT)
{
   pT->template func();   //<--- The compiler doesn't know what pT is, so you need to tell it that it's a template type
}
 
int main()
{
   Bar bar;
   Foo1(bar);
   Foo2(&bar);
}


          

Question: Why might the following fail to compile on some (if not all) compilers?

1:
2:
3:
std::string str = "HELLO WORLD";
std::transform(str.begin(), str.end(), str.begin(), tolower);


Answer: The function std::tolower comes in a number of overloads, including a template version on some compilers that take locale as a 2nd parameter. For this reason, the statement above may be seen as ambiguous and, thus, emit a compiler error.

          

Question: What is the difference between these two statements?

1:
2:
3:
Foo foo1 = new Foo;
Foo foo2 = new Foo();


Answer: The only difference is when creating intrinsic or POD (Plain Old Data) types. The first form does not execute the default constructor so the memory allocated is uninitialised, the second form runs the default constructor, which will initialise the memory allocated to zeros.

          

Question: Given the following function signature, write a function to reverse a linked list?

1:
2:
void reverse_single_linked_list(struct node** headRef);

T
Answer: Ideally, something like the following...

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
void reverse_single_linked_list(struct node** headRef)
{
   struct node* result = NULL;
   struct node* current = *headRef;
   struct node* next;

   while (current != NULL)
   {
       next = current->next; // tricky: note the next node
       current->next = result; // move the node onto the result
       result = current;
       current = next;
   }

   *headRef = result;
}


Starting from the head of the list for each iteration we make the next pointer of the 'current' node point to the previous node, we have to keep track of the 'next' node so as not to lose it as well as the 'result', which will end up being the new head once the complete list has been reversed.

Let's see how that works...

KEY
-------------------
H : Head
N : Node
C : Current
X : Next
R : Result
0 : Null terminator

Iteration 0

X ---> ?
C ---> H
R ---> 0

H ---> N1 ---> N2 ---> 0

Iteration 1

X ---> N1
C ---> N1
R ---> H

0 <--- H      N1 ---> N2 ---> 0

Iteration 2

X ---> N2
C ---> N2
R ---> N1

0 <--- H <--- N1      N2 ---> 0

Iteration 3

X ---> 0
C ---> 0
R ---> N2

0 <--- H <--- N1 <--- N2

NB. Using this technique to reverse a list you can find out if a linked list is self referential in linear 0(N) time. I'll leave it as an exercise for the reader to figure out how this works but try repeating the steps I show above but have an additional N3 node that references back to N1 and it should be pretty obvious why.

More info: Reversing a linked list using 3 pointers

          

Question: What does the following code do and what is the result of compilation?

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
#define SEALED(className) \
   className ## Sealer \
      { \
      private: className ## Sealer(){}; \
      friend class className; \
      }; \
class className : virtual private className ## Sealer \

class SEALED(MyClass) {};

class MyClassDisallowed : public MyClass {};

int main()
{
   // Perfectly legal construction
   MyClass myClass;

   // Illegal construction, super-class is sealed
   MyClassDisallowed myClassDisallowed;
}


Answer: It implements sealed class semantics (much like the C# keyword sealed).

It works by making the default constructor of the sealer class private, which means nothing can construct it. We then make the class we want to seal a friend of the sealer class and subclass it with virtual inheritance. As the subclass is a friend of the sealer class it can call the private constructor so we are able to instantiate instances of it. Since we virtually inherited the sealer class and since in C++ the top most sub-class of an inheritance tree always called the base classes constructor directly the fact that this constructor is inaccessible means the compiler will produce an error. Voila, we have sealed the class to prevent it being sub-classed.

Question: Without using Boost or Loki (or any other 3rd party framework), how can you determine if type X is convertible to type Y?

Answer: Something like the following 

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
#include 

// Some types
struct A{};
struct B:A{};
struct C{};
 
template 
struct is_convertible
{
private:
 struct True_ { char x[2]; };
 struct False_ { };
 
 static True_ helper(T2 const &);
 static False_ helper(...);
 
public:
 static bool const YES = (
  sizeof(True_) == sizeof(is_convertible::helper(T1()))
 );
}; 
template 
void foo(T1 const & t1, T2 const & t2)
{
 if(is_convertible::YES)
 {
  std::cout << "Type t1 is convertible to t2" << std::endl;
 }
 else
 {
  std::cout << "Type t1 is not convertible to t2" << std::endl;
 }
} 
 
int main(void)
{
 struct A a;
 struct B b;
 struct C c;
 
 foo(b,a);
 foo(c,a);
}


Great, but how does this work? An important factor to this technique is that in C++ a function can be overloaded with an ellipsis version and it will be called if and only if no other function of the same name can be found to match the calling parameter list. We take advantage of this by declaring (but not defining) two overloads of with same function name; one that take a reference to the type we're looking to see if we can convert to and the other takes the ... ellipsis.

The trick is to have the ellipsis version return a type that is a different size to more specific function. At compile time the compiler will use static polymorphism to decide which function to call and we can then use the sizeof operator on the function call to get the size of the functions return type that the compiler decided matched the calling parameter. If the types are convertible then the return type size will be that of the specific function taking a reference to the convertible type, otherwise the size will be that of the generic function that has the ellipsis parameter.

More info: Determining if a C++ type is convertible to another at compile time





Question: Without using Boost or Loki (or any other 3rd party framework), how would you implement static assertions?

Answer: Something like the following ...

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
template 
in-line void STATIC_ASSERT_IMPL()
{
 // B will be true or false, which will implicitly convert to 1 or 0
 char STATIC_ASSERT_FAILURE[B] = {0};
}
 
#define STATIC_ASSERT(B) STATIC_ASSERT_IMPL ()
 
int main()
{
 // On a Windows 32 bit platform with Visual Studio 2005 this will not fail
 STATIC_ASSERT(sizeof(int) == 4);
 
 // On a Windows 32 bit platform with Visual Studio 2005 this *will* fail
 STATIC_ASSERT(sizeof(int) == 3);
}


What on earth does that do then? Ok, so we've created a template function that takes a bool template value parameter (not a function parameter). Within the function create a char array and use the value of the bool to determine the size. In the case where the bool template value is true, a char array of one element will be created. Since this will never be used the compiler should happily optimize this away. In the case where the bool template value is false, the compiler will try to generate a template function that creates a char array of zero elements. Since this is invalid C++, a compiler error will ensue. Finally, we wrap it all up in a simple STATIC_ASSERT macro to make the code easier to read.



 




0 comments:

Post a Comment