<

C++ Q & A / Interview Practice Questions

Published on
92,288 Points
77,988 Views
28 Endorsements
Last Modified:
Awarded
Editor's Choice
Community Pick
evilrix
An expert in cross-platform ANSI C/C++ development, specialising in meta-template programming and low latency scalable architecture design.
How good is your C++? Below is a collection of questions that I have built up over time that test how well someone who claims to know C++ actually does. Knowing the answers to these questions will probably help you avoid making a lot of the mistakes made by less experienced C++ programmers that can lead to some rather hard to isolate defects. It's also worth noting that a lot of these questions are typical interview questions, so if you are currently career searching it would pay you well to use this article to bring your skills up to speed.

The format of this article is not typical of most EE articles, in so far as it is not about a specific topic; rather it should be seen as a Q & A (or FAQ). To get the best out of it try and answer each question yourself before looking at the answer. Each answer will give you, along with the actual answer, an explanation of why the answer is what it is and, where appropriate, links to external resources will be provided for further reading.

If you get more than 90% right you are well on your way to being a C++ guru. If there is something you don't understand, we have a great team of experts in the C++ Technical Area just itching to help you, so feel free to pop over there and ask an expert.

Warning: what follows is aimed at intermediate to advanced C++ programmers. It is not intended to be a primer for anyone wanting to learn C++. If you don't know the difference between a ternary operator and a double de-referenced pointer this is not the article for you. If you are looking to learn C++ I can recommend the following web sites as good places to start:

C++ Tutorial
C++ FAQ Lite
C++ Reference
C Programming

         

Question: What is the output of the following code?

#include <iostream>
#include <algorithm>
#include <iterator>

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<int>(std::cout, " "));
}

Open in new window


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?

#include <cstring>

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;
}

Open in new window


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?

int main(){}

Open in new window


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?

char * p = "foobar";

Open in new window


b) How do these 2 lines of code differ?

char const * p = "foobar";
char const s[] = "foobar";

Open in new window


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?

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)();
}

Open in new window


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?

int i = 0;
char a[2] = { char() };

a[i++] = ++i;

Open in new window


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?

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

Open in new window


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?

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

Open in new window


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?

// Translation unit "foo.cpp"
int const FOO = 1;

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

Open in new window


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

// 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()

Open in new window


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?

Foo f(Bar());

Open in new window


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?

typedef std::vector<int> vec_t;
vec_t v(100);
vec_t::const_iterator i = v.begin() + 50;
v.push_back(5);
(*i) = 5;

Open in new window


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?

std::auto_ptr<int> pInt = new int;

Open in new window


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.

std::auto_ptr<int> pInt(new int);

Open in new window


         

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

std::auto_ptr<int> pInt(new int[10]);

Open in new window


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?

std::string foo() throw()
{
	std::string s("hello world");
	return s;
}

Open in new window


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.

// Example of RVO
Bar Foo()
{
	return Bar();
}

Open in new window



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.

// Example of NRVO
Bar Foo()
{
	Bar bar;
	return bar;
}

Open in new window


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?

// Snippet 1
struct Bar{};
Bar foo(){ return Bar(); }
Bar & b = foo();

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

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

Open in new window


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?

template <typename T> struct A{};
template <typename T> struct B : A<T>{};

template <template <typename> class C, typename T>
C<T> foo()
{
	C<T> ct;
	return ct;
}

int main()
{
	A<int> const & a = foo<B, int>();
}

Open in new window


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?

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?
}

Open in new window


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?

struct Bar
{
   template <typename U>
   void func() const { }

   template <typename U>
   static void sfunc() { }
};

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

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

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

Open in new window


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.

struct Bar
{
   template <typename U>
   void func() const { }
 
   template <typename U>
   static void sfunc() { }
};
 
template <typename T>
void Foo1(T const & t)
{
   t.template func<int>();     //<--- The compiler doesn't know what t is, so you need to tell it that it's a template type
   T::template sfunc<int>();   //<--- The compiler doesn't know what T is, so you need to tell it that it's a template type
}
 
template <typename T>
void Foo2(T const * pT)
{
   pT->template func<int>();   //<--- 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);
}

Open in new window


         

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

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

Open in new window


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?

Foo foo1 = new Foo;
Foo foo2 = new Foo();

Open in new window


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?

void reverse_single_linked_list(struct node** headRef);

Open in new window

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

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;
}

Open in new window


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?

#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;
}

Open in new window


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.

More info: Sealing a C++ Class

         

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

#include <iostream>

// Some types
struct A{};
struct B:A{};
struct C{};
 
template <typename T1, typename T2>
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 <typename T1, typename T2>
void foo(T1 const & t1, T2 const & t2)
{
	if(is_convertible<T1, T2>::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);
}

Open in new window


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 ...

template <bool B>
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 <B>()
 
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);
}

Open in new window


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.

More info: Static assertions in C++

         

Hopefully the questions in this Q&A have been useful. Of course, this isn't an exhaustive list of questions but if you are looking for an advanced C++ development position you should be able to follow most if not all of the questions and answers presented here. If you're hungry for more C++ Q & A try taking a look at these:

http://www.techinterviews.com/interview-questions/c
http://maxnoy.com/interviews.html
http://vijayinterviewquestions.blogspot.com/

If you'd like to test your C++ skill level to see how good you really are, try taking the Brainbench free C++ test (see if you can beat my score of a 96%) or the (unfortunately not free but there is a sample) IKM Teckchek assessment (see if you can beat my score of a 98%). Incidentally, a lot of employers use these tests so if you've practiced them before-hand you should do much better.

I can also thoroughly recommend the following two online books to brush up on your advanced C++ skill-set:
C++ Coding Standards: 101 Rules, Guidelines, and Best Practices
Effective C++ and More Effective C++

If you are job hunting, good luck and I hope you found this article helpful. If you are reading this because you're just a hardcore C++ nut (like me) then I hope I challenged you a little.

Thanks for reading.

NB. If you liked this article, please don't forget to let me know by clicking the 'yes' link at the top of the page where it asks, "Was this article helpful?".
28
Author:evilrix
Ask questions about what you read
If you have a question about something within an article, you can receive help directly from the article author. Experts Exchange article authors are available to answer questions and further the discussion.
Get 7 days free