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.

class Rectangle {
public:
	float x, y;
	float width, height;	
};

Rectangle r(); // parenthesis optional
r.x = 10;
r.y = 20;

Easy enough.

2. User-defined Default Constructor

If you provide a constructor with no arguments, this is also a type of default constructor.

class Rectangle {
public:
	Rectangle() {
		x = 10;
		y = 20;
		width = 30;
		height = 40;
	}
	float x, y;
	float width, height;	
};

Rectangle r(); // parenthesis optional

3. Constructor with arguments

Of course, you can require arguments in your constructor.

class Rectangle {
public:
	Rectangle(float _x, float _y, float _width, float _height) {
		x = _x;
		y = _y;
		width = _width;
		height = _height;
	}
	float x, y;
	float width, height;	
};

Rectangle r(10, 20, 30, 40);

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:

Rectangle rectangles[100];

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:

Rectangle(float _x=10, float _y=20, float _width=30, float _height=40) {
	x = _x;
	y = _y;
	width = _width;
	height = _height;
}

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?

Rectangle r1(10, 20, 30, 40);
Rectangle r2 = r1;

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:

Rectangle(const Rectangle& mom) {
	x = mom.x;
	y = mom.y;
	width = mom.width;
	height = mom.height;
}

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

class Rectangle {
public:
	Rectangle();
	~Rectangle();
	Rectangle(const Rectangle& mom);
	void init(float _x=10, float _y=20, float _width=30, float _height=40);
	static int nRects;
};

Rectangle.cpp

int Rectangle::nRects = 0;

Rectangle::Rectangle() {
	nRects++;
}

Rectangle::~Rectangle() {
	nRects--;
}

Rectangle::Rectangle(const Rectangle& mom) {
	init(mom.x, mom.y, mom.width, mom.height);
	nRects++;
}

void Rectangle::init(float _x, float _y, float _width, float _height) {
	x = _x;
	y = _y;
	width = _width;
	height = _height;
}

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.

Line(float x1, float y1, float x2, float y2) : p1(x1, y1), p2(x2, y2) {

}

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:

Logger::getInstance().open("logfile.txt");
Logger::getInstance().write( "Hello!" );

See this StackOverflow thread for more info.