C++ for C programmers, part 2 of 2
August 2010
This article lists the features C++ adds to C, from an ex-C programmer’s point of view. A couple of months ago I wrote part 1, which detailed the non-OO features. This second part details the object-oriented features, though I haven’t given them exactly watertight separation.
Again, this is a quick reference, and the idea is to follow the links to further information if you want to know more about any of them.
So, part 2 of 2, the OO features.
Class and (de)constructors
- Classes and the
class
keyword (of course). Think of them as your own datatypes. Or — and someone will probably tell me this is terrible advice — think of them asstruct
s with attached functions. An instance of a class is called an object, and when you callmyobject.func(42)
it’s like callingfunc(&myobject, 42)
in C, but the first parameter is implicit, andfunc
is in its own namespace (the class’s). - Along with instance data and instance functions (which act on the instance data), classes can also have static data and static functions — which act on the static data, of which there’s only one copy for all objects. You declare the static data inside the class, as in
class MyType { static int counter; }
, but for the static data to exist you also have to define it outside the class once, as inint MyType::counter;
- Speaking of
static
, beware the static initialization order fiasco. If you have two static objects,a
andb
, anda
’s constructor uses something fromb
, you’ve got a 50% chance of failure — unfortunately, the constructor-call order for statics is undefined. You can prevent it without too much trouble. - Initialization lists for a constructor look funny to a C programmer:
MyType() : _myint(5) { ... }
is basically equivalent toMyType() { _myint = 5; }
— but the former is somewhat better and more idiomatic C++. - In C++,
struct MyType { ... }
is equivalent to class MyType { public: ... }
. So if you don’t care about privacy, you can save 6 characters on every class you create. :-) - A class’s constructor is called when an object of that class is instantiated, to create the object from dust. You can overload the constructor if you want to construct your object from different things (e.g., an int as well as a float).
- A class’s destructor is called when the object goes out of scope or is deleted, and frees or closes any resources the object has allocated or opened.
- Related to this is RAII, or Resource Acquisition Is Initialization. This is a nasty name for what I call AC/DC — Acquire in Constructor, Destructor does Cleanup. C++ nicely calls object destructors in a deterministic way (when the object goes out of scope, even if an exception occurs).
- A default constructor is a constructor with no arguments (or one that can be called with no arguments). If you don’t define one, C++’s default default constructor simply calls the default constructor of all its member objects and base classes. The default constructor is called when you declare an object without arguments like
MyType x;
or an array of objects likeMyType x[10];
- The copy constructor initialises a new object from an existing one, usually copying its values, and you define one like this:
MyType(const MyType& x) { ... }
. The copy constructor is called when you defineMyType a = b;
or when you pass or return aMyType
to a function by value, but note that it’s not called when you just saya = b;
(that’s not constructing an object, so it calls the assignment operator instead). - The assignment operator,
operator=
, is called when you saya = b;
and botha
andb
are instances ofMyType
. The default assignment operator is simply to copy all members, which is fine until you have to allocate buffers in your constructor — instead of having C++ copy pointers, you’ll want to overloadoperator=
. An assignment operator will typically allocate new resources fora
and copyb
‘s data, then freea
‘s old buffers. - A constructor with a single argument is automatically used as a “type converter”. So if you define
MyType(int n) { ... }
, you can say things likeMyType a = 5;
and it’ll automatically call that constructor. - But this can be dangerous, and you can tell the compiler not to convert automatically by prefixing your constructor with the
explicit
keyword, as inexplicit MyType(int n) { ... }
- A method can be declared
const
, which tells the compiler it won’t change the object, it’ll just look at it. They’re used all the time forgetData()
-style accessor functions. - The rarely-used
mutable
keyword means you can change this variable even in a const method. MSDN has a succinct explanation and example. - Inside a member function, the
this
keyword is a pointer to the current object. Unlikeself
in Python, in C++ it’s usually implicit. It’d be slightly nicer as a reference, and Stroustrup indicated he’d have madethis
a reference if references were part of the languages way back when.
Virtual functions
- One of the most important constructs in OO C++, a virtual function, is a method which can be overridden by a subclass. If you have a
Cat
class with avirtual void make_noise()
method, then your subclassesHouseCat
andLion
can override that with meowing and roaring behaviour. If you have a list ofCat
s and want them all to make noise, you simply loop through the list and callmake_noise()
on each one (the virtual function table takes care of the details). Note that in languages like Python, all functions act virtual. - A pure virtual function is a virtual function in your parent class that must be overridden in the base class — all cats make noise. The class with the pure virtual function is called “abstract”, and you can’t instantiate it directly. A class with only pure virtual functions defines an interface or API. The weirdest thing about pure virtual functions are their weird
=0
syntax: you define them like so:virtual void make_noise() = 0;
- You should define your destructor as virtual when your class has one or more virtual functions. If you don’t, and you delete an object through a base class pointer, the subclass’s destructor won’t get called — memory leak. Stroustrup has an example (albeit contrived) of how this works.
Inheritance and friends
- Classes can have
public
,private
, andprotected
sections. Private variables and methods are only accessible by the class’s own members, not even its subclasses — useful for implementation details you don’t want to expose as part of the class’s API. Protected members are accessible by subclasses, but not anyone else. And public members are accessible by everyone. - However, if B is defined as a friend class or friend member inside class A, B can access A’s private parts (yes, it should probably be called
married_to
). The C++ FAQ Lite has a good section onfriend
ship. - Public inheritance is the usual form of “a Lion is a Cat” inheritance (
class Lion : public Cat { ... };
). I’ve never used private and protected inheritance, and can’t quite figure them out, so I’m leaving that as an exercise for the reader. :-) - Multiple inheritance is when your class inherits from two base classes at once. Don’t get too clever with this! For instance, if Dad pays the bills, you could have
class Dad : public Human, public BreadWinner { ... };
Type casting
- Right up there with the
=0
notation and using bit-shift operators for I/O are the C++ cast operators. You can use regular C casts likei = (int)f;
but they’re discouraged. You usestatic_cast<type>(expression)
to cast between numeric types or to cast a pointer down in an inheritance chain (you’ve got to be somewhat careful). Usereinterpret_cast<type>(expression)
to cast expression’s bits to type (know what a core dump is first). And useconst_cast
to cast away the const attribute on something. - There’s also
dynamic_cast<type>(expression)
to cast pointers or references, which is safe but requires compiler run-time overhead. It checks the type of expression at run-time, and if it’s able to be casted to type, all good. If not,dynamic_cast
returnsNULL
(or throws an exception for references). - More generally, there’s RTTI (Run-Time Type Information):
dynamic_cast
is part of that, and the other part is thetypeid(expression)
operator, which returns a type information object about the given expression. Useful for comparing types at run-time, printing type names, etc.
New and delete
- To allocate an object on the heap, use
Class* pobj = new Class;
— this tries to allocate memory (throwing an exception if that fails) and callsClass
’s constructor on the object. You can allocate arrays with new likeint* pint = new int[5];
. For super-advanced control over hownew
allocates, read up on placementnew
and overloading new. - Of course, the opposite of
new
isdelete
, which calls the class’s destructor and then frees the memory new allocated. Just like withmalloc()
andfree()
, for everynew
there must be an equal and oppositedelete
. Hint: use AC/DC (RAII) if you can.
Operator overloading
- You can overload pretty much all operators in C++ to work with your own classes. If someone else has done this for you, great (e.g.,
std::string
overloading+
to mean concatenation). But mainly because of C++’s memory and exception model, there are many gotchas. Do your homework before getting too clever. - Note that you can’t overload these operators:
.
?:
::
and the semi-obscure.*
operator. Also, you can’t add operators of your own (say ** to mean to-the-power-of).
Templates
- It took me a while to grok templates, but they’re simple at heart — just a way to avoid repeating yourself. Instead of writing an
IntArray
class and then finding you needFloatArray
andStringArray
later, you can just write a genericArray<T>
class which is anArray
of class or typeT
. That’s a class template. - Function templates let you write generic functions instead of classes. So instead of writing
min(int x, int y)
andmin(double x, double y)
you can just write amin<T>(T x, T y)
. Then you can callmin()
on whatever number types you want and the compiler will generate the right code for each (yes, all this usually means bigger executables). - Template arguments can have defaults. So you can have say
LargeNumber<class T =long>
— if you instantiate a plainLargeNumber
it’ll use longs, but you can override it to use doubles by sayingLargeNumber<double>
.
Exceptions
- Exception handling in C++, like operator overloading, is a really nice idea that can turn sour pretty quickly if you don’t keep it in the fridge (i.e., handle with care). A number of C++ coding standards simply disallow exceptions, perhaps for good reason. At least make sure you learn the way of the RAII before delving here.
Well, thanks for listening!
Comments
Andy Morris 10 Aug 2010, 15:15
The point of private inheritance is the same as it is everywhere – it’s an implementation detail. It’s almost the same as just including an instance of the base class as a private member and using it, but there’s the added benefit that you can access the protected members on the base class.
In the situation where you are building object A and your implementation needs to include object B, but you need to access to B’s protected method foo(), you could:
- Privately derive A from B, allowing A’s implementation to call this->foo(), or
- Create a class C that derives publicly from B, somehow expose C.foo(), then include C in A
The private inheritance method is probably better in this case.
— Ayjay on Fedang/coding/C++
Ben 10 Aug 2010, 15:25
Thanks, Andy — that explains it pretty well.
Berwyn 3 Aug 2012, 10:27
Stroustrup has moved his faqs to a domain with his own name. I have updated the rotten links to it.