Digital said:
I'm not the world's greatest C++ programmer, so I had a hard
time with these. Some help would be appreciated.
The next problem is your interviewer might not be the world's greatest,
either... ;-)
1. Comment on the declaration of function Bar() below:
class Foo
{
static int Bar(int i) const;
}
(I said that since Bar is a static method, it can only access
static variables, not member variables, so the const is
irrelevant.)
I suspect it should not even compile.
2. If you have virtual functions in your class, do you need a virtual
destructor? Why?
(My gut answer was that no, you don't NEED a virtual destructor
unless the derived class is handling its own dynamically-managed
data.)
In this case, you must answer twice; both the safest way and the technically
correct way. The safest way is "all destructors are virtual unless profiling
reveals the need to remove a virtual". That's safe, because a teammate might
write a class that inherits your class, and then write a 'delete' that uses
a pointer to your class, not the derived class. Deleting such an object,
without a virtual destructor, is undefined behavior.
The technical answer is no, you never are required to make a destructor
virtual, if you know that nobody will ever delete it through a pointer to
the base class.
And the other virtual methods are only indirectly indicated. They don't
influence the technical or semantic decision to make the destructor virtual.
Indirectly, semantically, you should not inherit from a class with no
virtual methods, hence you should never delete a pointer to its base class.
At least the question wasn't "If a destructor releases no resources, must it
be virtual?" That implies the common fallacy that deleting a pointer to the
base class will only create undefined behavior if it creates a leak. The
undefined behavior actually starts at destructor time, regardless what then
leaks.
3. Comment on the following function. What would you change? State
your assumptions:
string & Foo()
{
string default_string = "default answer";
for (vector<node>::iterator iter = mynodes.begin();
iter != mynodes.end();
iter++)
{
cout << iter;
}
return default_string;
}
It returns a reference to a destructed local, so that's undefined.
Next, vector<node> is not typedeffed.
Next, the default_string has nothing to do with the for-loop, which could be
just a decoy. Regardless, you should announce the function does two
independent things and should be split in two.
Next, cout << iter is poorly defined to produce something useless, possibly
a pointer address. If operator<< is defined for 'node', then cout << * iter
might be more useful.
Next, all iterators should always be called 'it'. ;-)
4. When would you use private inheritence? (I have never personally
used this.)
Per /C++ Coding Standards/ by Sutter and Alex A., you should prefer it to
public inheritance.
Consider the Abstract Template Pattern, but where only the derived classes
know they call a secret parent that then, in turn, calls their specialized
methods to help it perform their templated action.
(Warning: That's not a C++ template...)
5. Where in memory is the virtual function lookup table stored?
(I found this one to be way too detailed for me.)
I don't know how Standard this answer is, but it's stored in the initialized
global data segment. This fact (even its accurate version) should never be
used in practice, except in the sickest debugging situations.
6. Consider member initialisation lists. Why are they needed?
Because they explicitly declare intent, to both your colleagues and to the
compiler.
Why are
they considered more efficient than initialising members in the body of
the constructor?
Because the compiler can generate the most efficient code to implement them.
They are defined to occur in the same order as the members appear in the
class's definition, so the compiler could get as efficient as an unrolled
loop to stomp them all in.
Further, some systems can provide a warning if you write them out of order.
This helps your code self-correct.
Next, your constructor's body should be exception neutral and safe. So
putting pointer(NULL) in the member list, and then a pointer=new in the
body, can lead to a safer 'delete' if the constructor then blows. (But the
destructor won't call, so still prefer smart pointers!)
Next, if any member's initialization throws an exception, the stack unwinder
will call the destructor of each fully constructed member, in reverse order.
In general, efficiency is a poor taskmaster. Premature optimization is the
root of all evil. In this case, you may err on the side of efficiency if the
result is also _cognitively_ efficient. Member initialization lists are
easier to comprehend, after you train to recognize them as important parts
of constructors. So follow this rule: Never enter the opening { of a
constructor with any member in an uninitialized state.
You can get that rule easily by only using smart data members, such as smart
pointers and other contained objects with default constructors.