Intermediate openFrameworks - Constructors
Introduction
What you already know: In object-oriented programming, [the constructor](http://en.wikipedia.org/wiki/Constructor_(object-oriented_programming) is a function that has the same name as the class and is automatically called when the object is created. It is typically used for initializing member variables.
What you might not know: In c++, there are a few different flavors of constructor and it’s handy to be familiar with them.
First things first
Before we start, if you are coming from a language with garbage collection/automatic memory management: forget everything that you know about the new
operator. In many languages, new
is used every time you want to construct an object, like: Widget myWidget = new Widget();
or var grades = new Array()
. But as you might have noticed, this is not always necessary in c++. The new
operator actually deals with dynamically allocated memory and has no special connection to a class constructor. Dynamically allocated memory is covered in the pointers lesson, but you should read this one first.
With that out of the way, we can get started.
1. Implicit default constructor
The most basic kind of constructor is no constructor at all. If you don’t supply a constructor, you are said to be using the implicit default constructor.
Easy enough.
2. User-defined Default Constructor
If you provide a constructor with no arguments, this is also a type of default constructor.
3. Constructor with arguments
Of course, you can require arguments in your constructor.
TIP: Notice that all of the arguments to the constructor start with a “_”. This has no special meaning and is just a convention to help distinguish the constructor arguments from the member variables.
There is one problem with only providing a constructor with arguments. If I used the class definition above and then try to declare an array of Rectangle objects like this:
I get the following error:
constructor.cpp:19: error: no matching function for call to ‘Rectangle::Rectangle()'
constructor.cpp:7: note: candidates are: Rectangle::Rectangle(float, float, float, float)
constructor.cpp:5: note: Rectangle::Rectangle(const Rectangle&)
Pretty straightforward error message if you understand that, when you declare an array of objects, they are actually constructed immediately using the default constructor.
This error message is saying:
- By making an array of objects, you are implicitly saying that you want to use a default constructor, but there isn’t a default constructor in the Rectangle class.
- Instead, try one of these:
- There is one constructor that takes a bunch of arguments on line 7
- There is also an implicit copy constructor.
Default Arguments
One way around this problem is to provide default arguments like so:
Providing defaults for every argument is like having a default constructor because it doesn’t require any arguments.
4. Implicit Copy Constructor
Question: what happens in c++ when you assign one object to another?
Answer: a copy constructor is called. If you do not provide an explicit copy constructor (more on this in the next section), you are said to be using the implicit copy constructor, which the compiler provides one for you. In the case of our Rectangle class, it would look like this:
Using this copy constructor is fine sometimes, but if you want to have more control over what happens during a copy, you’ll want to override this function.
5. User-defined Copy Constructor
If you want to provide your own copy constructor, you simply provide a constructor with a very particular signature: it should accept an un-editable (const) reference to another object of the same type.
#copies = 1
Notice that, when the object is copied, instead of copying all of the variables as-is, I increment the copy
member.
TIP: One situation where you would want to define your own copy constructor is if you need a “deep copy” of an object, but this is outside of the scope of this lesson. More about this at the wikipedia article.
Destructors!
A destructor is simply a constructor with a tilde ‘~’ in front, and it gets called when the object is destroyed: that is, when it goes out of scope, the program ends, or it is explicitly deleted.
Run this code and you will get a file named log.txt
that contains the text:
opening log
Hello, World!
closing log
Check out line 12. Every coder knows it’s good style to close all file handles when you are done with them. This is the perfect job for a destructor.
Proper c++ Classes
For the purpose of brevity, I haven’t been using very good coding style in these examples. In fact, classes are always supposed to use 2 files: a header file and an implementation file.
header: Logger.h
implementation: Logger.cpp
log.cpp
To compile this with g++, you could run the following:
g++ -o log Logger.cpp log.cpp
Extra Credit
Here are a few extra concepts that deal with constructors
1. Object Counter
You can use your constructor(s) and destructor with a static member variable to keep a count of how many objects have been created in total. But you have to be careful to cover all of your bases:
Rectangle.h
Rectangle.cpp
Notice that I increment nRects not only in the default constructor, but also in teh copy constructor.
2. Initialization List
Take a look at the following and assume (for some reason) that I can’t change the Point class to have a default constructor.
If you try to run this code, you will get an error saying that the Point class has no default constructor. This is because, when you construct a Line object, the first thing that happens is that the constructors for all of the member variables are called first. So, before your Line constructor runs, p1
and p2
need to be constructed. However, the Point
class doesn’t have a default constructor, leading to errors.
On solution is to use an initialization list.
This allows you to call the constructors of p1
and p2
explicitly before your Line constructor is run.
3. Singleton pattern
One useful application of a user-defined copy constructor is the singleton pattern.
Suppose I want to ensure that there is only one instance of a particular class in my application. A popular example is a logging class.
Using this pattern, any class or function in your application can call Logger::getInstance() to access the logging functionality without having to pass a reference to every class that wants to use it.
You can also use it like this:
See this StackOverflow thread for more info.