C++ is a programming language closely related to C
It is reasonably compatible - you can call C libraries from C++ and C++ libraries from C (same ABI)
But it is not fully compatible:
C++ is much bigger and more complicated than C
Most advantages apply to multi-module programs, which are barely touched on in this unit
The main extra concepts and features are object oriented, but in an old, complicated and confusing style
It is better to study object oriented programming 'properly' with Java in the second teaching block, then C++ becomes easy
If you program in C in a very carefully disciplined way, there is nothing you can't do
The main advantage of C++ is the availability of lots of good libraries, e.g. for cryptography, gaming and so on
Also, programming can be safer, more convenient, and better structured, especially for variable-size data
However, C++ is often badly misused, or programs are over-engineered, so it too has to be used in a very carefully disciplined way
The remaining slides aim to give you a glimpse of what C++ is like
You can think of C++ as C with extra features (though some minor details are different because the standards never quite agree)
Probably, the most important extra concept is the class:
class point { int x, y; };
To begin with, think of class
as just a synonym for
struct
, defining fields of objects
You get the type point
for free, as if you had
typed
typedef class point point;
A class gives you a constructor for free:
point *p = new point();
This is a safer version of a C memory allocation:
point *p = malloc(sizeof(point));
In fact you can still write this malloc call in C++, except you have to add a cast
Unlike structs, everything in a class is private by default
p->x = 3; // ERROR
You can use the public:
label to make items public:
class point {
public:
int x, y;
};
point.cpp#include <stdio.h>
class point { public: int x, y; };
int main() {
point *p;
p = new point();
p->x = 2;
printf("%d\n", p->x);
}
A C++ programmer would not normally use stdio
or
printf
because C++ has its own IO library
Most C compilers are also C++ compilers, called with a different name to show that you want C++
So point.cpp
would be compiled with one of:
g++ -std=c++11 -g -o point point.cpp clang++ -std=c++11 -g -o point point.cpp
You may find you need to use -o point.exe
with
clang
on Windows, as with C
class point {
int x, y;
public:
point(int x0, int y0) { x = x0; y = y0; }
int getX() { return x; }
int getY() { return y; }
};
A method is a function 'attached' to an object
point *p = new point(2, 3);
printf("%d\n", p->getX());
point(int x0, int y0) { x = x0; y = y0; }
This line overrides the default constructor
A constructor has the same name as the class,
and has an implicit return type (point *
)
printf("%d\n", p->getX());
In this line, p->getX()
is a method call
In C, it would be getX(p)
, the object you are calling the method
on is an implicit extra first argument
An advantage is that with p->getX()
lots of classes can have
different getX
methods, i.e. a class provides a namespace
int getX() { return x; }
In this line, x
is accessed directly (methods in a class can
access its private fields)
The equivalent in C would be:
int getX(point *this) { return this->x; }
The name this
in C++ can be used to access the implicit extra
argument, if you need to
Here's the point type implemented in a modular way in C, using an opaque structure declaration in the header
point.hstruct point;
typedef struct point point;
point *newPoint(int x0, int y0);
int getX(point *p);
int getY(point *p);
point.c#include "point.h"
point *newPoint(int x0, int y0) { ... }
int getX(point *p) { ... }
int getY(point *p) { ... }
Here's the 'standard' approach in C++
point.hppclass point {
int x, y;
public:
point(int x0, int y0);
int getX();
int getY();
};
point.cpp#include "point.hpp"
point::point(int x0, int y0) { ... }
int point::getX() { return x; }
int point::getY() { return y; }
The class definition is in the header:
point.hppclass point {
int x, y;
...
};
The fields are private so that, even though they are exposed in the header, other modules can't access them
They have to be in the header, so that an implicit sizeof
can
be used in calls to new point(...)
from other modules
The methods are declared rather than being defined:
point.hppclass point {
...
public:
point(int x0, int y0);
int getX();
int getY();
};
The methods are defined with a point::
prefix:
point.cpppoint::point(int x0, int y0) { ... }
The point::
prefix says that the method is being defined inside
the point
class, even though the definition is physically outside
the class
There is a weakness in the 'standard' approach:
display.hpp#include <SDL2/SDL.h>
class display {
SDL_Window *window;
...
};
Even though the fields are private, their types are not, so in this case an SDL header has to be included
And now the SDL API has leaked all over the program
Why does the leak of SDL matter?
display.hpp#include <SDL2/SDL.h>
...
Because you are tempted to mention SDL in other modules, for example
using SDL_Colour
to pass colours around
And then your whole program is SDL-dependent
SDL should be included like this:
display.cpp#include <SDL2/SDL.h>
...
It should be included in display.cpp
in order to implement the
display module, not in display.hpp
to make it
visible everywhere
Then a change to a different graphics library affects only one module
The official fix uses the "PIMPL strategy", but that seems too complex because each object is represented by two objects (it is a good example of the way over-engineering creeps into C++ programs
Instead, there are some simpler ways to fix the problem
In the header file, declare type struct Window;
and give the window field the type struct Window *
In the implementation file, use casts to convert between
struct Window *
and SDL_Window *
This has the advantage of sticking with the official C++ way of presenting classes
Here's a second fix - change the header file to:
point.hppclass point {
public:
int getX();
int getY();
};
point *newPoint(int x0, int y0);
The point
type is effectively an interface, with no data
Here's the fixed cpp
file:
point.cpp#include "point.hpp"
class pointI: public point {
int x, y;
public:
pointI(int x0, int y0) { x = x0; y = y0; }
int getX() { return x; }
int getY() { return y; }
};
point *newPoint(int x0, int y0) {
return new pointI(x0, y0);
}
point::getX() { return ((pointI *) this)->getX(); }
point::getY() { return ((pointI *) this)->getY(); }
point.cppclass pointI: public point {
int x, y;
public:
pointI(int x0, int y0) { x = x0; y = y0; }
int getX() { return x; }
int getY() { return y; }
};
The first line makes pointI
a child class of
point
It is the "point
implementation" class
No other module has direct access to it - the only access is via the
point
interface
newPoint
functionpoint.cpppoint *newPoint(int x0, int y0) {
return new pointI(x0, y0);
}
Instead of a constructor, an ordinary C-like function is defined for building
new point
objects, so there is no longer any need for the fields to
be exposed
The only subtlety is that the value returned has type pointI *
whereas the function is declared to return point *
But that's OK because the child class relationship means that a
pointI
is a point
Functions like newPoint
are called factory functions,
and are potentially much more flexible than conventional constructors
In fact, according to some people (including me), constructors using the
new
keyword are a mistake in object oriented languages generally
(C++, C#, Java, JavaScript, Python, ...) and should be eliminated
point::getX() { return ((pointI *) this)->getX(); }
point::getY() { return ((pointI *) this)->getY(); }
These functions cast the implicit extra argument this
from
point *
to pointI *
, so as to replace the
point
versions of getX/getY
with the
pointI
versions
The third fix will get rid of the need for these stubs
Here's the fixed header file:
point.hppclass point {
public:
virtual int getX() = 0;
virtual int getY() = 0;
};
point *newPoint(int x0, int y0);
Here's the fixed cpp
file:
point.cpp#include "point.hpp"
class pointI: public point {
int x, y;
public:
pointI(int x0, int y0) { x = x0; y = y0; }
int getX() { return x; }
int getY() { return y; }
};
point *newPoint(int x0, int y0) {
return new pointI(x0, y0);
}
point.hppvirtual int getX() = 0;
The getX
method is declared virtual
That means the class has a child class which overrides the getX
method, and in a call p->getX()
, the compiler does not know whether
variable p
holds an object of class point
or its child
class pointI
, so it has to compile the call differently
point.hppvirtual int getX() = 0;
How does a call p->getX()
get compiled?
It can't be compiled as getX(p)
because there are two versions
of getX
Instead, each object has an implicit extra class
field, and the
call is implemented as p->class->getX(p)
(which can be done
in C using function pointers)
point.hppvirtual int getX() = 0;
The =0
at the end of the declaration of getX
means that it is an abstract method
That means there isn't a version for the point
class, only for
child classes
And so the point
class has no constructor and you can't
make point
objects, only objects of child classes
Abstract classes are sometimes called interfaces
Only two features of C++, classes and modules, have been covered, though they are key features
The advantages are fairly minor, because almost everything in C++ boils down to some fairly simple variations on C
There are rather a lot of new details to understand and master
But mastery opens up a new world of libraries and design possibilities