![]() |
| C++ syntax, semantics and concepts questions |
An object is a package that contains related data
and instructions. The data relates to what the object represents, while the
instructions define how this object relates to other objects and itself.
![]()
A message is a signal from one object to another
requesting that a computation take place. It is roughly equivalent to a function
call in other languages.
![]()
A class defines the characteristics of a certain
type of object. It defines what its members will remember, the messages to which
they will respond, and what form the response will take.
![]()
An individual object that is a member of some
class.
![]()
Given a class, a super-class is the basis of the
class under consideration. The given class is defined as a subset (in some
respects) of the super-class. Objects of the given class potentially posses all
the characteristics belonging to objects of the super-class.
![]()
Inheritance is property such that a parent (or
super) class passes the characteristics of itself to children (or sub) classes
that are derived from it. The sub-class has the option of modifying these
characteristics in order to make a different but fundamentally related class
from the super-class.
![]()
An object's message protocol is the exact form of
the set of messages to which the object can respond.
![]()
Polymorphism refers to the ability of an object to
respond in a logically identical fashion to messages of the same protocol,
containing differing types of objects. Consider 1 + 5 and 1 + 5.1. In the
former, the message "+ 5" is sent to an object of class integer (1). In the
later, the message "+ 5.1" is sent to the same integer object. The form of the
message (its protocol) is identical in both cases. What differs is the type of
object on the right-hand side of these messages. The former is an integer object
(5) while the later is a floating point object (5.1). The receiver (1) appears
(to other objects) to respond in the same way to both messages. Internally,
however, it knows that it must treat the two types of objects differently in
order to obtain the same overall response.
![]()
These represent an object's private memory. They
are defined in an object's class.
![]()
These represent a class's memory which it shares
with each of its instances.
![]()
A method is a class's procedural response to a
given message protocol. It is like the definition of a procedure in other
languages.
![]()
A constructors and destructors are methods defined
in a class that are invoked automatically when an object is created or
destroyed. They are used to initialize a newly allocated object and to cleanup
behind an object about to be removed.
![]()
Comparison: C++ is an extension to the C language. When C++ is used as a procedural language, there are only minor syntactical differences between them.
Contrast: When used as a procedural language, C++ is a better C because:
As an object oriented language,
C++ introduces much of the OOP paradigm while allowing a mixture of OOP and
procedural styles.
![]()
It is the process of, and ability to redefine the
way an object responds to a C++ operator symbol. This would be done in the
object's class definition.
![]()
They are objects corresponding to a program's
default input and output files.
![]()
The procedural paradigm performs computation through a step-by-step manipulation of data items. Solving problems this way is akin to writing a recipe. ie: All the ingredients (data items) are defined. Next a series of enumerated steps (statements) are defined to transform the raw ingredients into a finished meal.
The object oriented model, in contrast, combines
related data and procedural information into a single package called an object.
Objects are meant to represent logically separate entities (like real world
objects). Objects are grouped together (and defined by) classes. (This is
analogous to user defined data types in procedural languages.) Classes may
pass-on their "makeup" to classes derived from them. In this way, Objects that
are of a similar yet different nature need not be defined from scratch.
Computation occurs though the intercommunication of objects. Programming this
way is like writing a play. First the characters are defined with their
attributes and personalities. Next the dialog is written so that the
personalities interact. The sum total constitutes a drama.
![]()
By using the extern "C" linkage specification around the C function declarations.
You should know about mangled function names and
type-safe linkages. Then you should explain how the extern "C" linkage
specification statement turns that feature off during compilation so that the
linker properly links function calls to C functions. Another acceptable answer
is "I don't know. We never had to do that." Merely describing what a linker does
would indicate to me that you do not understand the issue that underlies the
question.
![]()
The scope resolution operator permits a program to reference an identifier in the global scope that has been hidden by another identifier with the same name in the local scope.
The answer can get complicated. It should start
with "colon-colon," however. (Some readers had not heard the term, "scope
resolution operator," but they knew what :: means. You should know the formal
names of such things so that you can understand all communication about them.)
If you claim to be well into the design or use of classes that employ
inheritance, you tend to address overriding virtual function overrides to
explicitly call a function higher in the hierarchy. That's good knowledge to
demonstrate, but address your comments specifically to global scope resolution.
Describe C++'s ability to override the particular C behavior where identifiers
in the global scope are always hidden by similar identifiers in a local scope.
![]()
The default member and base class access specifiers are different.
This is one of the commonly misunderstood aspects
of C++. Believe it or not, many programmers think that a C++ struct is just like
a C struct, while a C++ class has inheritance, access specifiers, member
functions, overloaded operators, and so on. Some of them have even written books
about C++. Actually, the C++ struct has all the features of the class. The only
differences are that a struct defaults to public member access and public base
class inheritance, and a class defaults to the private access specifier and
private base class inheritance. Getting this question wrong does not necessarily
disqualify you because you will be in plenty of good company. Getting it right
is a definite plus.
![]()
Two.
There are two formats for initializers in C++ as shown in Example 1. Example 1(a) uses the traditional C notation, while Example 1(b) uses constructor notation. Many programmers do not know about the notation in Example 1(b), although they should certainly know about the first one. Many old-timer C programmers who made the switch to C++ never use the second idiom, although some wise heads of C++ profess to prefer it.
A reader wrote to tell me of two other ways, as
shown in Examples 2(a) and 2(b), which made me think that maybe the answer could
be extended even further to include the initialization of an int function
parameter with a constant argument from the caller.
![]()
The throw operation calls the destructors for automatic objects instantiated since entry to the try block.
Exceptions are in the mainstream of C++ now, so most programmers, if they are familiar with setjmp and longjmp, should know the difference. Both idioms return a program from the nested depths of multiple function calls to a defined position higher in the program. The program stack is "unwound" so that the state of the program with respect to function calls and pushed arguments is restored as if the calls had not been made. C++ exception handling adds to that behavior the orderly calls to the destructors of automatic objects that were instantiated as the program proceeded from within the try block toward where the throw expression is evaluated.
It's okay to discuss the notational differences between the two idioms. Explain the syntax of try blocks, catch exception handlers, and throw expressions. Then specifically address what happens in a throw that does not happen in a longjmp. Your answer should reflect an understanding of the behavior described in the answer just given.
One valid reason for not knowing about exception handling is that your experience is exclusively with older C++ compilers that do not implement exception handling. I would prefer that you have at least heard of exception handling, though.
It is not unusual for C and C++ programmers to be unfamiliar with setjmp/ longjmp. Those constructs are not particularly intuitive. A C programmer who has written recursive descent parsing algorithms will certainly be familiar with setjmp/ longjmp. Others might not, and that's acceptable. In that case, you won't be able to discuss how setjmp/longjmp differs from C++ exception handling, but let the interview turn into a discussion of C++ exception handling in general. That conversation will reveal to the interviewer a lot about your overall understanding of C++.
delete this;
It's not a good practice.
A good programmer will insist that the statement is never to be used if the class is to be used by other programmers and instantiated as static, extern, or automatic objects. That much should be obvious.
The code has two built-in pitfalls. First, if it executes in a member function for an extern, static, or automatic object, the program will probably crash as soon as the delete statement executes. There is no portable way for an object to tell that it was instantiated on the heap, so the class cannot assert that its object is properly instantiated. Second, when an object commits suicide this way, the using program might not know about its demise. As far as the instantiating program is concerned, the object remains in scope and continues to exist even though the object did itself in. Subsequent dereferencing of the pointer can and usually does lead to disaster.
A reader pointed out that a class can ensure that its objects are instantiated on the heap by making its destructor private. This idiom necessitates a kludgy DeleteMe kind of function because the instantiator cannot call the delete operator for objects of the class. The DeleteMe function would then use "delete this."
I got a lot of mail about this issue. Many
programmers believe that delete this is a valid construct. In my experience,
classes that use delete this when objects are instantiated by users usually
spawn bugs related to the idiom, most often when a program dereferences a
pointer to an object that has already deleted itself.
![]()
A constructor that has no arguments or one where all the arguments have default argument values.
If you don't code a default constructor, the
compiler provides one if there are no other constructors. If you are going to
instantiate an array of objects of the class, the class must have a default
constructor.
![]()
A constructor that accepts one argument of a different type.
The compiler uses this idiom as one way to infer
conversion rules for a class. A constructor with more than one argument and with
default argument values can be interpreted by the compiler as a conversion
constructor when the compiler is looking for an object of the type and sees an
object of the type of the constructor's first argument.
![]()
A copy constructor constructs a new object by using the content of the argument object. An overloaded assignment operator assigns the contents of an existing object to another existing object of the same class.
First, you must know that a copy constructor is one that has only one argument, which is a reference to the same type as the constructor. The compiler invokes a copy constructor wherever it needs to make a copy of the object, for example to pass an argument by value. If you do not provide a copy constructor, the compiler creates a member-by-member copy constructor for you.
You can write overloaded assignment operators that take arguments of other classes, but that behavior is usually implemented with implicit conversion constructors. If you do not provide an overloaded assignment operator for the class, the compiler creates a default member-by-member assignment operator.
This discussion is a good place to get into why
classes need copy constructors and overloaded assignment operators. By
discussing the requirements with respect to data member pointers that point to
dynamically allocated resources, you demonstrate a good grasp of the problem.
![]()
There are three acceptable answers: "Never," "Rarely," and "When the problem domain cannot be accurately modeled any other way."
There are some famous C++ pundits and luminaries who disagree with that third answer, so be careful.
Let's digress to consider this issue lest your
interview turn into a religious debate. Consider an Asset class, Building class,
Vehicle class, and CompanyCar class. All company cars are vehicles. Some company
cars are assets because the organizations own them. Others might be leased. Not
all assets are vehicles. Money accounts are assets. Real-estate holdings are
assets. Some real-estate holdings are buildings. Not all buildings are assets.
Ad infinitum. When you diagram these relationships, it becomes apparent that
multiple inheritance is an intuitive way to model this common problem domain.
You should understand, however, that multiple inheritance, like a chainsaw, is a
useful tool that has its perils, needs respect, and is best avoided except when
nothing else will do. Stress this understanding because your interviewer might
share the common bias against multiple inheritance that many object-oriented
designers hold.
![]()
The simple answer is that a virtual destructor is one that is declared with the virtual attribute.
The behavior of a virtual destructor is what is
important. If you destroy an object through a pointer or reference to a base
class, and the base-class destructor is not virtual, the derived-class
destructors are not executed, and the destruction might not be complete.
![]()
A specialized class "is a" specialization of another class and, therefore, has the ISA relationship with the other class. An Employee ISA Person. This relationship is best implemented with inheritance. Employee is derived from Person. A class may have an instance of another class. For example, an Employee "has a" Salary, therefore the Employee class has the HASA relationship with the Salary class. This relationship is best implemented by embedding an object of the Salary class in the Employee class.
The answer to this question reveals whether you have an understanding of the fundamentals of object-oriented design, which is important to reliable class design.
There are other relationships. The USESA
relationship is when one class uses the services of another. The Employee class
uses an object (cout) of the ostream class to display the employee's name
onscreen, for example. But if you get ISA and HASA right, you usually don't need
to go any further.
![]()
When you are designing a generic class to contain or otherwise manage objects of other types, when the format and behavior of those other types are unimportant to their containment or management, and particularly when those other types are unknown (thus the genericity) to the designer of the container or manager class.
Prior to templates, you had to use inheritance;
your design might include a generic List container class and an
application-specific Employee class. To put employees in a list, a
ListedEmployee class is multiply derived (contrived) from the Employee and List
classes. These solutions were unwieldy and error-prone. Templates solved that
problem.
![]()
int __ = 0;
main() {
int ___ = 2;
printf("%d\n", ___ + __);
}
On GNU compiler both C and C++ give output as 2. I
believe other C++ compiler will complain "syntax error", whereas C compiler will
give 2.
![]()
C is based on structured programming whereas C++
supports the object-oriented programming paradigm.Due to the advantages inherent
in object-oriented programs such as modularity and reuse, C++ is preferred.
However almost anything that can be built using C++ can also be built using C.
![]()
The access privileges in C++ are private, public
and protected. The default access level assigned to members of a class is
private. Private members of a class are accessible only within the class and by
friends of the class. Protected members are accessible by the class itself and
it's sub-classes. Public members of a class can be accessed by anyone.
![]()
Data Encapsulation is also known as data hiding.
The most important advantage of encapsulation is that it lets the programmer
create an object and then provide an interface to the object that other objects
can use to call the methods provided by the object. The programmer can change
the internal workings of an object but this transparent to other interfacing
programs as long as the interface remains unchanged.
![]()
Inheritance is the process of deriving classes from
other classes. In such a case, the sub-class has an 'is-a' relationship with the
super class. For e.g. vehicle can be a super-class and car can be a sub-class
derived from vehicle. In this case a car is a vehicle. The super class 'is not
a' sub-class as the sub- class is more specialized and may contain additional
members as compared to the super class. The greatest advantage of inheritance is
that it promotes generic design and code reuse.
![]()
Multiple Inheritance is the process whereby a
sub-class can be derived from more than one super class. The advantage of
multiple inheritance is that it allows a class to inherit the functionality of
more than one base class thus allowing for modeling of complex relationships.
The disadvantage of multiple inheritance is that it can lead to a lot of
confusion when two base classes implement a method with the same name.
![]()
Polymorphism refers to the ability to have more
than one method with the same signature in an inheritance hierarchy. The correct
method is invoked at run-time based on the context (object) on which the method
is invoked. Polymorphism allows for a generic use of method names while
providing specialized implementations for them.
![]()
When a class member is declared to be of a static
type, it means that the member is not an instance variable but a class variable.
Such a member is accessed using Classname.Membername (as opposed to
Object.Membername). Const is a keyword used in C++ to specify that an object's
value cannot be changed.
![]()
Memory is allocated in C using malloc() and freed
using free(). In C++ the new() operator is used to allocate memory to an object
and the delete() operator is used to free the memory taken up by an object.
![]()
UML refers to Unified Modeling Language. It is a
language used to model OO problem spaces and solutions.
![]()
A shallow copy simply creates a new object and
inserts in it references to the members of the original object. A deep copy
constructs a new object and then creates in it copies of each of the members of
the original object.
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
Note: I've only asked this question once; and yes,
he understood that I was asking him how cfront was put together.
![]()
#include
class A {
public:
A() {
this->Foo();
}
virtual void Foo() {
cout << "A::Foo()" << endl;
}
};
class B : public A {
public:
B() {
this->Foo();
}
virtual void Foo() {
cout << "A::Foo()" << endl;
}
};
int main(int, char**)
{
B objectB;
}
p {return 0;
}
![]()
#include
class A {
public:
A() {
cout << "A::A()" << endl;
}
~A() {
cout << "A::~A()" << endl; throw "A::exception";
}
};
class B {
public:
B() {
cout << "B::B()" << endl; throw "B::exception";
}
~B() {
cout << "B::~B()";
}
};
int main(int, char**) {
try {
cout << "Entering try...catch block" << endl;
}
p {A objectA;
B objectB;
}
p {cout << "Exiting try...catch block" << endl;
} catch (char* ex) {
cout << ex << endl;
}
}
p {return 0;
}
![]()
![]()