Study Notes on LearnCpp (Part III - Virtual Functions, Templates)


Source: LearnCpp.com by Alex

Virtual Functions

Pointers and references to the base class of derived objects

1
2
3
4
5
Derived derived(5);

// These are both legal!
Base &rBase = derived;
Base *pBase = &derived;

It turns out that because rBase and pBase are a Base reference and pointer, they can only see members of Base (or any classes that Base inherited). So even though Derived::getName() shadows (hides) Base::getName() for Derived objects, the Base pointer/reference can not see Derived::getName(). Consequently, they call Base::getName(), which is why rBase and pBase report that they are a Base rather than a Derived.

Note that this also means it is not possible to call Derived::getValueDoubled() using rBase or pBase. They are unable to see anything in Derived.

Want to take a guess what virtual functions are for? :) hhh

Virtual functions and polymorphism

A virtual function is a special type of function that, when called, resolves to the most-derived version of the function that exists between the base and derived class. This capability is known as polymorphism. A derived function is considered a match if it has the same signature (name, parameter types, and whether it is const) and return type as the base version of the function. Such functions are called overrides.

To make a function virtual, simply place the “virtual” keyword before the function declaration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Base {
public:
virtual const char* getName() { return "Base"; } // note addition of virtual keyword
};

class Derived: public Base {
public:
virtual const char* getName() { return "Derived"; }
};

int main() {
Derived derived;
Base &rBase = derived;
std::cout << "rBase is a " << rBase.getName() << '\n';
// output: rBase is a Derived

return 0;
}

Because rBase is a reference to the Base portion of a Derived object, when rBase.getName() is evaluated, it would normally resolve to Base::getName(). However, Base::getName() is virtual, which tells the program to go look and see if there are any more-derived versions of the function available between Base and Derived. In this case, it will resolve to Derived::getName()!

A word of warning: the signature of the derived class function must exactly match the signature of the base class virtual function in order for the derived class function to be used. If the derived class function has different parameter types, the program will likely still compile fine, but the virtual function will not resolve as intended.

Use of the virtual keyword

If a function is marked as virtual, all matching overrides are also considered virtual, even if they are not explicitly marked as such. However, having the keyword virtual on the derived functions does not hurt, and it serves as a useful reminder that the function is a virtual function rather than a normal one. Consequently, it’s generally a good idea to use the virtual keyword for virtualized functions in derived classes even though it’s not strictly necessary.

Note that: return types must be matched either.

Do not call virtual functions from constructors or destructors

Here’s another gotcha that often catches unsuspecting new programmers. You should not call virtual functions from constructors or destructors. Why?

Remember that when a Derived class is created, the Base portion is constructed first. If you were to call a virtual function from the Base constructor, and Derived portion of the class hadn't even been created yet, it would be unable to call the Derived version of the function because there’s no Derived object for the Derived function to work on. In C++, it will call the Base version instead.

A similar issue exists for destructors. If you call a virtual function in a Base class destructor, it will always resolve to the Base class version of the function, because the Derived portion of the class will already have been destroyed.

Rule: Never call virtual functions from constructors or destructors

The downside of virtual functions

Since most of the time you’ll want your functions to be virtual, why not just make all functions virtual? The answer is because it’s inefficient — resolving a virtual function call takes longer than resolving a regular one. Furthermore, the compiler also has to allocate an extra pointer for each class object that has one or more virtual functions. We’ll talk about this more in future lessons in this chapter.

The override and final specifiers, and covariant return types

To address some common challenges with inheritance, C++11 added two special identifiers to C++: override and final. Note that these identifiers are not considered keywords — they are normal identifiers that have special meaning in certain contexts.

Although final isn’t used very much, override is a fantastic addition that you should use regularly. In this lesson, we’ll take a look at both, as well as one exception to the rule that virtual function override return types must match.

Consider the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A {
public:
virtual const char* getName1(int x) { return "A"; }
virtual const char* getName2(int x) { return "A"; }
};

class B : public A {
public:
virtual const char* getName1(short int x) { return "B"; }
// note: parameter is a short int
virtual const char* getName2(int x) const { return "B"; }
// note: function is const
};

int main() {
B b;
A &rBase = b;
std::cout << rBase.getName1(1) << '\n'; // output A
std::cout << rBase.getName2(2) << '\n'; // output A

return 0;
}

In this particular case, because A and B just print their names, it’s fairly easy to see that we messed up our overrides, and that the wrong virtual function is being called. However, in a more complicated program, where the functions have behaviors or return values that aren’t printed, such issues can be very difficult to debug.

To help address the issue of functions that are meant to be overrides but aren’t, C++11 introduced the override specifier. Override can be applied to any override function by placing the specifier in the same place const would go. If the function does not override a base class function, the compiler will flag the function as an error.

Like @Override in Java!

1
2
3
4
5
6
7
public:
virtual const char* getName1(short int x) override { return "B"; }
// compile error, function is not an override
virtual const char* getName2(int x) const override { return "B"; }
// compile error, function is not an override
virtual const char* getName3(int x) override { return "B"; }
// okay, function is an override of A::getName3(int)

There is no performance penalty for using the override specifier, and it helps avoid inadvertent errors. Consequently, we highly recommend using it for every virtual function override you write to ensure you’ve actually overridden the function you think you have.

Rule: Apply the override specifier to every intended override function you write.

The final specifier

There may be cases where you don’t want someone to be able to override a virtual function, or inherit from a class. The final specifier can be used to tell the compiler to enforce this. If the user tries to override a function or class that has been specified as final, the compiler will give a compile error.

1
2
3
4
5
6
// class A
virtual const char* getName() { return "A"; }
// class B
virtual const char* getName() override final { return "B"; }
// class C - compile error
virtual const char* getName() override { return "C"; }

In the case where we want to prevent inheriting from a class, the final specifier is applied after the class name:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A {
public:
virtual const char* getName() { return "A"; }
};

class B final : public A { // note use of final specifier here
public:
virtual const char* getName() override { return "B"; }
};

class C : public B { // compile error: cannot inherit from final class
public:
virtual const char* getName() override { return "C"; }
};

Covariant return type

There is one special case in which a derived class virtual function override can have a different return type than the base class and still be considered a matching override. If the return type of a virtual function is a pointer or a reference to a class, override functions can return a pointer or a reference to a derived class. These are called covariant return types. Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Base {
public:
// This version of getThis() returns a pointer to a Base class
virtual Base* getThis() { std::cout << "called Base::getThis()\n"; return this; }

void printType() { std::cout << "returned a Base\n"; }
};

class Derived : public Base {
public:
// Normally override functions have to return objects of the same type as the base function
// However, because Derived is derived from Base, it's okay to return Derived* instead of Base*

virtual Derived* getThis() { std::cout << "called Derived::getThis()\n"; return this; }

void printType() { std::cout << "returned a Derived\n"; }
};

int main() {
Derived d;
Base *b = &d;
d.getThis()->printType(); // calls Derived::getThis(), returns a Derived*, calls Derived::printType
b->getThis()->printType(); // calls Derived::getThis(), returns a Base*, calls Base::printType

// output
// called Derived::getThis()
// returned a Derived
// called Derived::getThis()
// returned a Base
}

Note that some older compilers (e.g. Visual Studio 6) do not support covariant return types.

One interesting note about covariant return types: C++ can’t dynamically select types, so you’ll always get the type that matches the base version of the function being called.

In the above example, we first call d.getThis(). Since d is a Derived, this calls Derived::getThis(), which returns a Derived. This Derived is then used to call non-virtual function Derived::printType().

Now the interesting case. We then call b->getThis(). Variable b is a Base pointer to a Derived object. Base::getThis() is virtual function, so this calls Derived::getThis(). Although Derived::getThis() returns a Derived, because base version of the function returns a Base, the returned Derived is upcast to a Base. And thus, Base::printType() is called.

In other words, in the above example, you only get a Derived* if you call getThis() with an object that is typed as a Derived object in the first place. (6^6)

Virtual destructors, virtual assignment, and overriding virtualization

Although C++ provides a default destructor for your classes if you do not provide one yourself, it is sometimes the case that you will want to provide your own destructor (particularly if the class needs to deallocate memory). You should always make your destructors virtual if you’re dealing with inheritance. Consider the following example:

1
2
3
4
5
6
7
8
9
// class Base
// ~Base (not virtual)
// class Derived
// ~Derived (not virtual)
int main() {
Derived *derived = new Derived(5);
Base *base = derived;
delete base; // it will call the Base destructor
}

However, we really want the delete function to call Derived’s destructor (which will call Base's destructor in turn), otherwise m_array will not be deleted. We do this by making Base’s destructor virtual:

Using virtual:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base {
public:
virtual ~Base() {
std::cout << Calling ~Base() << "\n";
}
};
class Derived : public Base {
private:
int *m_array;
public:
Derived(int length) {
m_array = new int[length];
}

virtual ~Dervied() { // note: virtual
std::cout << Calling ~Derived() << "\n";
delete[] m_array;
}
};

// output
Calling ~Derived()
Calling ~Base()

Rule: Whenever you are dealing with inheritance, you should make any explicit destructors virtual.

Virtual assignment

It is possible to make the assignment operator virtual. However, unlike the destructor case where virtualization is always a good idea, virtualizing the assignment operator really opens up a bag full of worms and gets into some advanced topics outside of the scope of this tutorial. Consequently, we are going to recommend you leave your assignments non-virtual for now, in the interest of simplicity.

Ignoring virtualization

Very rarely you may want to ignore the virtualization of a function. For example, consider the following code:

1
2
3
4
5
6
7
8
9
class Base {
public:
virtual const char* getName() { return "Base"; }
};

class Derived: public Base {
public:
virtual const char* getName() { return "Derived"; }
};

There may be cases where you want a Base pointer to a Derived object to call Base::getName() instead of Derived::getName(). To do so, simply use the scope resolution operator:

1
2
3
4
5
6
7
8
int main() {
Derived derived;
Base &base = derived;
// Calls Base::getName() instead of the virtualized Derived::getName()
std::cout << base.Base::getName() << "\n";

return 0;
}

Should we make all destructors virtual

More

Two recommendations are as follows:

  • If you intend your class to be inherited from, make sure your destructor is virtual.
  • If you do not intend your class to be inherited from, mark your class as final. This will prevent other classes from inheriting from it in the first place, without imposing any other use restrictions on the class itself.

Early binding and late binding

Binding refers to the process that is used to convert identifiers (such as variable and function names) into addresses. Although binding is used for both variables and functions, in this lesson we’re going to focus on function binding. (identifiers == symbols?)

Most of the function calls the compiler encounters will be direct function calls. A direct function call is a statement that directly calls a function. For example:

1
2
3
4
5
6
7
8
void printValue(int value) {
std::cout << value << "\n";
}

int main() {
printValue(5); // This is a direct function call
return 0;
}

Direct function calls can be resolved using a process known as early binding. Early binding (also called static binding) means the compiler (or linker I think it should be the linker) is able to directly associate the identifier name (such as a function or variable name) with a machine address. Remember that all functions have a unique address. So when the compiler (or linker) encounters a function call, it replaces the function call with a machine language instruction that tells the CPU to jump to the address of the function.

Late Binding

In some programs, it is not possible to know which function will be called until runtime (when the program is run). This is known as late binding (or dynamic binding). In C++, one way to get late binding is to use function pointers. The function that a function pointer points to can be called by using the function call operator (()) on the pointer.

1
2
3
4
5
6
7
8
9
int add(int x, int y) {
return x + y;
}

int main() {
int (*pFun)(int, int) = add;
std::cout << pFun(5, 3) << "\n";
return 0;
}

The compiler is unable to use early binding to resolve the function call pFcn(x, y) because it can not tell which function pFcn will be pointing to at compile time!

Late binding is slightly less efficient since it involves an extra level of indirection.

  • With early binding, the CPU can jump directly to the function’s address.
  • With late binding, the program has to read the address held in the pointer and then jump to that address. This involves one extra step, making it slightly slower. However, the advantage of late binding is that it is more flexible than early binding, because decisions about what function to call do not need to be made until run time.

The virtual table

To implement virtual functions, C++ uses a special form of late binding known as the virtual table. The virtual table is a lookup table of functions used to resolve function calls in a dynamic/late binding manner. The virtual table sometimes goes by other names, such as vtable, virtual function table, virtual method table, or dispatch table.

First, every class that uses virtual functions (or is derived from a class that uses virtual functions) is given its own virtual table. This table is simply a static array that the compiler sets up at compile time. A virtual table contains one entry for each virtual function that can be called by objects of the class. Each entry in this table is simply a function pointer that points to themost-derived function accessible by that class.

(markdown syntax compromise: *__vptr -> VP)

Second, the compiler also adds a hidden pointer to the base class, which we will call VP. VP is set (automatically) when a class instance is created so that it points to the virtual table for that class. Unlike the *this pointer, which is actually a function parameter used by the compiler to resolve self-references, VP is a real pointer. Consequently, it makes each class object allocated bigger by the size of one pointer. It also means that VP is inherited by derived classes, which is important.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Base {
public:
FunctionPointer *__vptr; // the compiler set automatically
virtual void function1() {};
virtual void function2() {};
};

class D1 : public Base {
public:
virtual void function1() {};
};

class D2 : public Base {
public:
virtual void function2() {};
};

Because there are 3 classes here, the compiler will set up 3 virtual tables: one for Base, one for D1, and one for D2. The compiler also adds a hidden pointer to the most base class that uses virtual functions.

When a class object is created, VP is set to point to the virtual table for that class. For example, when a object of type Base is created, VP is set to point to the virtual table for Base. When objects of type D1 or D2 are constructed, VP is set to point to the virtual table for D1 or D2 respectively (VP is inherited by Base class).

Now, let’s talk about how these virtual tables are filled out. Because there are only two virtual functions here, each virtual table will have two entries (one for function1(), and one for function2()). Remember that when these virtual tables are filled out, each entry is filled out with the most-derived function an object of that class type can call.

The virtual table for Base objects is simple. An object of type Base can only access the members of Base. Base has no access to D1 or D2 functions. Consequently, the entry for function1 points to Base::function1(), and the entry for function2 points to Base::function2().

The virtual table for D1 is slightly more complex. An object of type D1 can access members of both D1 and Base. However, D1 has overridden function1(), making D1::function1() more derived than Base::function1(). Consequently, the entry for function1 points to D1::function1(). D1 hasn’t overridden function2(), so the entry for function2 will point to Base::function2().

The virtual table for D2 is similar to D1, except the entry for function1 points to Base::function1() (because D2 does not override the function), and the entry for function2 points to D2::function2().

1
2
D1 d1;
Base *dPtr = &d1;

Note that because dPtr is a base pointer, it only points to the Base portion of d1. However, also note that VP is in the Base portion of the class, so dPtr has access to this pointer. Finally, note that dPtr->__vptr points to the D1 virtual table! Consequently, even though dPtr is of type Base, it still has access to D1's virtual table (through __vptr).

What happens if we call dPtr->function1()?

1
2
3
D1 d1;
Base *dPtr = &d1;
dPtr->function1();
  • First, the program recognizes that function1() is a virtual function.
  • Second, the program uses dPtr->__vptr to get to D1’s virtual table.
  • Third, it looks up which version of function1() to call in D1’s virtual table. This has been set to D1::function1(). Therefore, dPtr->function1() resolves to D1::function1()!

By using these tables, the compiler and program are able to ensure function calls resolve to the appropriate virtual function, even if you’re only using a pointer or reference to a base class!

Why slower?

Calling a virtual function is slower than calling a non-virtual function for a couple of reasons:

  • First, we have to use the *__vptr to get to the appropriate virtual table.
  • Second, we have to index the virtual table to find the correct function to call. Only then can we call the function.
  • As a result, we have to do 3 operations to find the function to call, as opposed to 2 operations for a normal indirect function call, or one operation for a direct function call. However, with modern computers, this added time is usually fairly insignificant.

Also as a reminder, any class that uses virtual functions has a __vptr, and thus each object of that class will be bigger by one pointer. Virtual functions are powerful, but they do have a performance cost.

Pure virtual functions, abstract base classes, and interface classes

C++ allows you to create a special kind of virtual function called a pure virtual function (or abstract function) that has no body at all! A pure virtual function simply acts as a placeholder that is meant to be redefined by derived classes.

1
2
3
4
5
6
7
8
9
10
class Base {
public:
const char* sayHi() { return "Hi"; } // a normal non-virtual function

virtual const char* getName() { return "Base"; } // a normal virtual function

virtual int getValue() = 0; // a pure virtual function

int doSomething() = 0; // Compile error: can not set non-virtual functions to 0
};

When we add a pure virtual function to our class, we are effectively saying, it is up to the derived classes to implement this function.

Using a pure virtual function has two main consequences:

  • First, any class with one or more pure virtual functions becomes an abstract base class, which means that it can not be instantiated!
  • Second, any derived class must define a body for this function, or that derived class will be considered an abstract base class as well.

Also, by making constructor protected, we don’t want people creating Base objects directly; but we still want derived classes to be able to use it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Animal {
protected:
std::string m_name;
Animal(std::string name) : m_name(name) {
// protected
}
virtual const char* speak() = 0;
public:
std::string getName() { return m_name; }
};

// but note that we can still add a body for speak

const char* Animal::speak() {
return "buzz";
}

class Cow : public Animal {
public:
Cow(std::string name) : Animal(name) {
// foo
}
virtual const char* speak() { return "Moo"; }
};

When providing a body for a pure virtual function, the body must be provided separately (not inline).This paradigm can be useful when you want your base class to provide a default implementation for a function, but still force any derived classes to provide their own implementation. However, if the derived class is happy with the default implementation provided by the base class, it can simply call the base class implementation directly. For example:

1
2
3
4
virtual const char* speak() {
return Animal::speak();
// actually, it is this->Animal::speak()
}

Interface classes

An interface class is a class that has no member variables, and where all of the functions are pure virtual! In other words, the class is purely a definition, and has no actual implementation. Interfaces are useful when you want to define the functionality that derived classes must implement, but leave the details of how the derived class implements that functionality entirely up to the derived class.

1
2
3
4
5
6
7
8
class IErrorLog {
public:
virtual bool openLog(const char *filename) = 0;
virtual bool closeLog() = 0;
virtual bool writeError(const char *errorMessage) = 0;
virtual ~IErrorLog() {} // make a virtual destructor in case we delete an IErrorLog pointer, so the proper derived destructor is called
// Any class inheriting from IErrorLog must provide implementations for all three functions in order to be instantiated.
};

Because interfaces have no data and no function bodies, they avoid a lot of the traditional problems with multiple inheritance while still providing much of the flexibility.

Pure virtual functions and the virtual table

Abstract classes still have virtual tables, as these can still be used if you have a pointer or reference to the abstract class. The virtual table entry for a pure virtual function will generally either contain a null pointer, or point to a generic function that prints an error (sometimes this function is named __purecall) if no override is provided.

Virtual base classes

Diamond Problem (Multiinheritance)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class PoweredDevice {
public:
PoweredDevice(int power) {
std::cout << "PoweredDevice: " << power << "\n";
}
};

class Scanner : public PoweredDevice {
public:
Scanner(int scanner, int power) : PoweredDevice(power) {
std::cout << "Scanner: " << "scanner" << "\n";
}
};

class Printer : public PoweredDevice {
public:
Printer(int printer, int power) : PoweredDevice(power) {
std::cout << "Printer: " << printer << "\n";
}
};

class Copier : public Scanner, public Printer {
public:
Copier(int scanner, int printer, int power)
: Scanner(scanner, power), Printer(printer, power) {

}
};

If you were to create a Copier class object, by default you would end up with two copies of the PoweredDevice class — one from Printer, and one from Scanner. This has the following structure:

1
2
3
4
5
6
7
8
9
10
11
12
int main() {
Copier copier(1, 2, 3);

/* output:
PoweredDevice: 3
Scanner: 1
PoweredDevice: 3
Printer: 2
*/

return 0;
}

As you can see, PoweredDevice got constructed twice. To share a base class, simply insert the “virtual” keyword in the inheritance list of the derived class. This creates what is called a virtual base class, which means there is only one base object that is shared. Here is an example (without constructors for simplicity) showing how to use the virtual keyword to create a shared base class:

1
2
3
4
class Powered Device {};
class Scanner : virtual public PoweredDevice {};
class Printer : virtual public PoweredDevice {};
class Copier : public Scanner, public Printer {};

Now, when you create a Copier class, you will get only one copy of PoweredDevice that will be shared by both Scanner and Printer.

However, this leads to one more problem: if Scanner and Printer share a PoweredDevice base class, who is responsible for creating it? The answer, as it turns out, is Copier. The Copier constructor is responsible for creating PoweredDevice. Consequently, this is one time when Copier is allowed to call a non-immediate-parent constructor directly.

Note that:

  • First, virtual base classes are always created before non-virtual base classes, which ensures all bases get created before their derived classes.

  • Second, note that the Scanner and Printer constructors still have calls to the PoweredDevice constructor. When creating an instance of Copier, these constructor calls are simply ignored because Copier is responsible for creating the PoweredDevice, not Scanner or Printer. However, if we were to create an instance of Scanner or Printer, those constructor calls would be used, and normal inheritance rules apply.

  • Third, if a class inherits one or more classes that have virtual parents, the most derived class is responsible for constructing the virtual base class. In this case, Copier inherits Printer and Scanner, both of which have a PoweredDevice virtual base class. Copier, the most derived class, is responsible for creation of PoweredDevice. Note that this is true even in a single inheritance case: if Copier was singly inherited from Printer, and Printer was virtually inherited from PoweredDevice, Copier is still responsible for creating PoweredDevice.

  • Fourth, all classes inheriting a virtual base class will have a virtual table, even if they would normally not have one otherwise, and thus be larger by a pointer.

Because Scanner and Printer derive virtually from PoweredDevice, Copier will only be one PoweredDevice subobject. Scanner and Printer both need to know how to find that single PoweredDevice subobject, so they can access its members (because after all, they are derived from it). This is typically done through some virtual table magic (which essentially stores the offset from each subclass to the PoweredDevice subobject).

Object slicing

What happens if instead of setting a Base reference or pointer to a Derived object, we simply assign a Derived object to a Base object?

1
2
3
Derived derived(5);
Base base = derived; // what happens here?
std::cout << "base is a " << base.getName() << " and has value " << base.getValue() << '\n';

Remember that derived has a Base part and a Derived part. When we assign a Derived object to a Base object, only the Base portion of the Derived object is copied. The Derived portion is not. In the example above, base receives a copy of the Base portion of derived, but not the Derived portion. That Derived portion has effectively been “sliced off”. Consequently, the assigning of a Derived class object to a Base class object is called object slicing (or slicing for short).

Because variable base does not have a Derived part, base.getName() resolves to Base::getName().

Used conscientiously, slicing can be benign. However, used improperly, slicing can cause unexpected results in quite a few different ways.

Example:

1
2
3
4
5
6
7
8
9
10
11
// class Derived
void printName(const Base base) { // note: base passed by value, not reference
std::cout << "I am a " << base.getName() << '\n';
}

int main() {
Derived d(5);
printName(d); // oops, didn't realize this was pass by value on the calling end

return 0;
}

When you wrote this program, you may not have noticed that base is a value parameter, not a reference. Therefore, when called as printName(d), we might have expected base.getName() to call virtualized function getName() and print “I am a Derived”, that is not what happens. Instead, Derived object d is sliced and only the Base portion is copied into the base parameter. When base.getName() executes, even though the getName() function is virtualized, there’s no Derived portion of the class for it to resolve to.

Slicing vectors (std::reference_wrapper<>)

Yet another area where new programmers run into trouble with slicing is trying to implement polymorphism with std::vector. Consider the following program:

1
2
3
4
5
6
7
8
9
10
11
12
13
std::vector<Base> v;
v.push_back(Base(5));
v.push_back(Derived(6)); // Derived is sliced

for (int count = 0; cout < v.size(); ++count) {
std::cout << "I am a " << v[count].getName()
<< "with value " << v[count].getValue() << "\n";
}

/*
I am a Base with value 5
I am a Base with value 6
*/

Fixing this is a little more difficult. Many new programmers try creating a std::vector of references to an object, like this:

Unfortunately, this won’t compile. The elements of std::vector must be assignable, whereas references can't be reassigned (only initialized).

1
2
3
4
std::vector<Base*> v;
v.push_back(new Base(5)); // add a Base object to our vector
v.push_back(new Derived(6)); // add a Derived object to our vector
// but later you need to delete the pointers by forloop

But it’s quite a bit of additional headache since you now have to deal with dynamic memory allocation.

There’s one other way to resolve this. The standard library provides a useful workaround: the std::reference_wrapper class. Essentially, std::reference_wrapper is a class that acts like a reference, but also allows assignment and copying, so it’s compatible with std::vector.

The good news is that you don’t really need to understand how it works to use it. All you need to know are three things:

  1. std::reference_wrapper lives in the <functional> header
  2. When you create your std::reference_wrapper wrapped object, the object can’t be an anonymous object (since anonymous objects have expression scope would leave the reference dangling)
  3. When you want to get your object back out of std::reference_wrapper, you use the get() member function.
1
2
3
4
5
6
7
8
9
10
11
std::vector<std::reference_wrapper<Base> > v;
// our vector is a vector of std::reference wrapper wrapped Base (not Base&)
Base b(5);
Derived d(6);
v.push_back(b);
v.push_back(d);

for (int count = 0; count < v.size(); ++count) {
std::cout << v[count].get().getName() << "\n";
// avoids having to deal with dynamic memory
}

The Frankenobject

In the above examples, we’ve seen cases where slicing lead to the wrong result because the derived class had been sliced off. Now let’s take a look at another dangerous case where the derived object still exists!

Consider the following code:

1
2
3
4
Derived d1(5);
Derived d2(6);
Base &b = d2;
b = d1; // this line is problematic

The first three lines in the function are pretty straightforward. Create two Derived objects, and set a Base reference to the second one.

The fourth line is where things go astray. Since b points at d2, and we’re assigning d1 to b, you might think that the result would be that d1 would get copied into d2 — and it would, if b were a Derived. But b is a Base, and the operator= that C++ provides for classes isn't virtual by default. Consequently, only the Base portion of d1 is copied into d2.

As a result, you’ll discover that d2 now has the Base portion of d1 and the Derived portion of d2. In this particular example, that’s not a problem (because the Derived class has no data of its own), but in most cases, you’ll have just created a Frankenobject -- composed of parts of multiple objects. Worse, there’s no easy way to prevent this from happening (other than avoiding assignments like this as much as possible).

Note: (In non-inheritance case, types are not matched)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Test1 {};

class Test2 {};

int main() {
Test1 t1;
Test2 t2;
Test1 &a = t1;
a = t2;
/*
no viable overloaded operator=
no operator "=" matches these operands -- operand types are: Test1 = Test2
*/
return 0;
}

Although C++ supports assigning derived objects to base objects via object slicing, in general, this is likely to cause nothing but headaches, and you should generally try to avoid slicing. Make sure your function parameters are references (or pointers) and try to avoid any kind of pass-by-value when it comes to derived classes.

Note: when reassigning references or objects, consider potential object-slicing problems.

Dynamic casting

The need for dynamic_cast

When dealing with polymorphism, you’ll often encounter cases where you have a pointer to a base class, but you want to access some information that exists only in a derived class.

In a program, function getObject() always returns a Base pointer, but that pointer may be pointing to either a Base or a Derived object. In the case where the pointer is pointing to a Derived object, how would we call Derived::getName()?

(Base doesn’t getName(), m_name members)

One way would be to add a virtual function to Base called getName() (so we could call it with a Base object, and have it dynamically resolve to Derived::getName()). But what would this function return if you called it with a Base object? There isn’t really any value that makes sense. Furthermore, we would be polluting our Base class with things that really should only be the concern of the Derived class.

C++ provides a casting operator named dynamic_cast that can be used for downcasting. Although dynamic casts have a few different capabilities, by far the most common use for dynamic casting is for converting base-class pointers into derived-class pointers.

1
2
3
4
Base *b = getObject(true);  // true means return a pointer to Base object
Derived *d = dynamic_cast<Derived*>(b);
// use dynamic cast to donvert Base pointer into Derived pointer
delete b;

dynamic_cast failure

But the above situation makes an assumption that b is pointing to a Derived object! When we try to dynamic_cast that to a Derived, it will fail, because the conversion can’t be made.

If a dynamic_cast fails, the result of the conversion will be a null pointer.

Because we haven’t checked for a null pointer result, we access d->getName(), which will try to dereference a null pointer, leading to undefined behavior (probably a crash).

Rule: Always ensure your dynamic casts actually succeeded by checking for a null pointer result.

Note that because dynamic_cast does some consistency checking at runtime (to ensure the conversion can be made), use of dynamic_cast does incur a performance penalty.

Also note that there are several cases where downcasting using dynamic_cast will not work:

  1. With protected or private inheritance.
  2. For classes that do not declare or inherit any virtual functions (and thus don’t have a virtual table).
    • although we set functions virtual in the base class, there is no virtual table (but a null VP pointer) if we don’t override the virtual functions (note: we don’t need to specify the virutal keyword in the subclass)
  3. In certain cases involving virtual base classes (see this page for an example of some of these cases, and how to resolve them).

Downcasting with static_cast

It turns out that downcasting can also be done with static_cast. The main difference is that static_cast does no runtime type checking to ensure that what you're doing makes sense. This makes using static_cast faster, but more dangerous. If you cast a Base to a Derived, it will “succeed” even if the Base pointer isn’t pointing to a Derived object. This will result in undefined behavior when you try to access the resulting Derived pointer (that is actually pointing to a Base object).

If you’re absolutely sure that the pointer you’re downcasting will succeed, then using static_cast is acceptable. One way to ensure that you know what type of object you’re pointing to is to use a virtual function to indicate a type of a class. Here’s one (not great because it uses a global variable) way to do that:

1
2
3
4
5
6
7
8
9
10
11
12
13
enum ClassID {
BASE,
DERIVED
};
// Base
virtual ClassID getClassID() { return BASE; }
// Derived
virtual ClassID getClassID() { return DERIVED; }
// main
Base *b = getObject(true);
if (b->getClassID() == DERIVED) {
// foo
}

dynamic_cast and references

Although all of the above examples show dynamic casting of pointers (which is more common), dynamic_cast can also be used with references. This works analogously to how dynamic_cast works with pointers.

Because C++ does not have a “null reference”, dynamic_cast can’t return a null reference upon failure. Instead, if the dynamic_cast of a reference fails, an exception of type std::bad_cast is thrown.

dynamic_cast vs static_cast

New programmers are sometimes confused about when to use static_cast vs dynamic_cast. The answer is quite simple: use static_cast unless you're downcasting, in which case dynamic_cast is usually a better choice. However, you should also consider avoiding casting altogether and just using virtual functions.

Downcasting vs virtual functions

There are some developers who believe dynamic_cast is evil and indicative of a bad class design. Instead, these programmers say you should use virtual functions.

In general, using a virtual function should be preferred over downcasting. However, there are times when downcasting is the better choice:

  • When you can not modify the base class to add a virtual function
    (e.g. because the base class is part of the standard library)
  • When you need access to something that is derived-class specific
    (e.g. an access function that only exists in the derived class which means not using polymorphism)
  • When adding a virtual function to your base class doesn’t make sense
    (e.g. there is no appropriate value for the base class to return). Using a pure virtual function may be an option here if you don’t need to instantiate the base class.

Printing inherited classes using operator<<

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Base {
public:
Base() {}
virtual void print() const { std::cout << "Base"; }
};

class Derived : public Base {
public:
Derived() {}
virtual void print() const override { std::cout << "Derived"; }
};

int main() {
Derived d;
Base &b = d;
b.print(); // will call Derived::print()

// if we want to mix it with cout, it's kind of messy
std::cout << "b is a ";
b.print(); // put in middle
std::cout << '\n';

return 0;
}

Our purpose is like this:

1
std::cout << "b is a " << b << '\n'; // much better

Let’s start by overloading operator<< in the typical way:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
class Base {
public:
Base() {}
virtual void print() const { std::cout << "Base"; }
friend std::ostream& operator<< (std::ostream &out, const Base &b) {
out << "Base";
return out;
}
};

class Derived : public Base {
public:
Derived() {}
virtual void print() const override {
std::cout << "Derived";
}
friend std::ostream& operator<< (std::ostream &out, const Derived &d) {
out << "Derived";
return out;
}
};

int main() {
Base b;
std::cout << b << "\n";
Derived d;
std::cout << d << "\n";
// okay

// but consider
Base &bref = d;
std::cout << bref << "\n";
/* output: Base (not what we want) */
/* this is because our version of operator<< isn't virtual */
return 0;
}

Can we make operator<< virtual? No!

  • First, only member functions can be virtualized.
  • Second, even if we could virtualize operator<< there’s the problem that the function parameters for Base::operator<< and Derived::operator<< differ. Consequently, the Derived version wouldn’t be considered an override of the Base version, and thus be ineligible for virtual function resolution.

The solution

First, we set up operator<< as a friend in our base class as usual. But instead of having operator<< do the printing itself, we delegate that responsibility to a normal member function that can be virtualized!

Here’s the full solution that works:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
class Base {
public:
Base() {}
friend std::ostream& operator<< (std::ostream &out, const Base &b) {
// Delegate printing responsibility for printing to member function print()
return b.print(out);
}
// We'll rely on member function print() to do the actual printing
// Because print is a normal member function, it can be virtualized
virtual std::ostream& print(std::ostream& out) const {
out << "Base";
return out;
}
};

class Derived : public Base {
public:
Derived() {}
// Note that we don't need to define an operator<< for each derived class!
// The version that handles Base objects works just fine for both Base objects and any class derived from Base!

virtual std::ostream& print(std::ostream& out) const override {
out << "Derived";
return out;
}
};

int main() {
Base b;
std::cout << b << '\n';
Derived d;
std::cout << d << "\n";

Base &bref = d;
std::cout << bref << "\n";

return 0;
}

Templates

Function templates

1
2
3
int max(int x, int y) {
return (x > y) ? x : y;
}

Having to specify different “flavors” of the same function where the only thing that changes is the type of the parameters can become a maintenance headache and time-waster, and it also violates the general programming guideline that duplicate code should be minimized as much as possible. Wouldn’t it be nice if we could write one version of max() that was able to work with parameters of ANY type?

In C++, function templates are functions that serve as a pattern for creating other similar functions. The basic idea behind function templates is to create a function without having to specify the exact type(s) of some or all of the variables. Instead, we define the function using placeholder types, called template type parameters. Once we have created a function using these placeholder types, we have effectively created a “function stencil”.

When you call a template function, the compiler “stencils” out a copy of the template, replacing the placeholder types with the actual variable types from the parameters in your function call! Using this methodology, the compiler can create multiple “flavors” of a function from one template! We’ll take a look at this process in more detail in the next lesson.

You can name your placeholder types almost anything you want, so long as it’s not a reserved word. However, in C++, it’s customary to name your template types the letter T (short for “Type”).

1
2
3
T max(T x, T y) {
return (x > y) ? x : y;
}

In order to make this work, we need to tell the compiler two things: First, that this is a template definition, and second, that T is a placeholder type. We can do both of those things in one line, using what is called a template parameter declaration:

1
2
3
4
template <typename T> // this is the template parameter declaration
T max(T x, T y) {
return (x > y) ? x : y;
}

To create a template type parameter, use either the keyword typename or class. There is no difference between the two keywords in this context, so which you use is up to you. Note that if you use the class keyword, the type passed in does not actually have to be a class (it can be a fundamental variable, pointer, or anything else that matches). Then you name your type (usually “T”, “T1”, “T2”, “S”).

One final note: Because the function argument passed in for type T could be a class type, and it’s generally not a good idea to pass classes by value, it would be better to make the parameters and return types of our templated function const references:

1
2
3
4
template <typename T>
const T& max(const T& x, const T& y) {
return (x > y) ? x : y;
}

Function template instances

C++ does not compile the template function directly. Instead, at compile time, when the compiler encounters a call to a template function, it replicates the template function and replaces the template type parameters with actual types. The function with actual types is called a function template instance.

1
2
3
4
template <typename T>
const T& max(const &T x, const T& y) {
return (x > y) ? x : y;
}

When compiling your program, the compiler encounters a call to the templated function:

1
int i = max(3, 7);  // calls max(int, int)

The compiler says, “oh, we want to call max(int, int)”. The compiler replicates the function template and creates the template instance max(int, int):

1
2
3
const int& max(const int &x, const int &y) {
return (x > y) ? x : y;
}

The compiler is smart enough to know it only needs to create one template instance per set of unique type parameters (per file). It’s also worth noting that if you create a template function but do not call it, no template instances will be created.

Using template with classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <typename T>
const T& max(const T& x, const T& y) {
return (x > y) ? x : y;
}

class Cents {
private:
int m_cents;
public:
Cents(int cents) : m_cents(cents) {}
};

int main() {
Cents nickle(5);
Cents dime(10);
Cents bigger = max(nikcle, dime);
return 0;
}

The complier will try to compile this function. See the problem here? C++ has no idea how to evaluate x > y! Consequently, this will produce a fairly-tame looking compile error, like this:

1
error C2676: binary '>': 'const Cents' does not define this operator or a conversion to a type acceptable to the predefined operator

To get around this problem, simply overload the > operator for any class we wish to use max() with:

1
2
3
friend bool operator> (const Cents &c1, const Cents &c2) {
return (c1.m_cents > c2.m_cents);
}

Another example

Calculate the average of a number of objects in an array:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <class T>
T average(T *array, int length) {
T sum = 0;
for (int count = 0; count < length; ++count)
sum += array[count];
sum /= length;
return sum;
}

int main() {
int array1[] = { 5, 3, 2, 1, 4 };
std::cout << average(array1, 5) << '\n';
double array2[] = { 3.12, 3.45, 9.23, 6.34 };
std::cout << average(array2, 4) << '\n';

return 0;
}

Now let’s see what happens when we call this function on our Cents class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Cents {
private:
int m_cents;
public:
Cents(int cents) : m_cents(cents) {}
friend bool operator> (const Cents &c1, const Cents &c2) {
return (c1.m_cents > c2.m_cents);
}
};

template <class T>
T average(T *array, int length) {
T sum = 0;
for (int count = 0; count < length; ++count)
sum += array[count];
sum /= length;
return sum;
}

int main() {
Cents array3[] = { Cents(5), Cents(10), Cents(15), Cents(14) };
std::cout << average(array3, 4) << "\n";

return 0;
}

The compiler goes berserk and produces a ton of error messages!

1
c:\consoleapplication1\main.cpp(55): error C2679: binary '<<': no operator found which takes a right-hand operand of type 'Cents' (or there is no acceptable conversion)

Remember what I said about crazy error messages? We hit the motherload! Despite looking intimidating, these are actually quite straightforward. The first line is telling you that it couldn’t find an overloaded operator<< for the Cents class. All of the lines in the middle are all of the different functions it tried to match with but failed. The last error points out the function call that spawned this wall of errors.

Remember that average() returns a Cents object, and we are trying to stream that object to std::cout using the << operator. However, we haven’t defined the << operator for our Cents class yet. Let’s do that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Cents {
private:
int m_cents;
public:
Cents(int cents) : m_cents(cents) {}

friend bool operator> (const Cents &c1, const Cents &c2) {
return (c1.m_cents > c2.cents);
}

friend std::ostream& operator<< (std::ostream &out, const Cents &cents) {
out << cents.m_cents << " cents";
return out;
}
};

Then it shows another error:

1
c:test.cpp(14) : error C2676: binary '+=' : 'Cents' does not define this operator or a conversion to a type acceptable to the predefined operator

The reason we are getting an error message is because of the following line: sum += array[count];

1
2
3
4
5
6
7
8
9
// overloaded member function
Cents& operator+= (Cents cents) {
m_cents += cents.m_cents;
return *this;
}
Cents& operator/= (int value) {
m_cents /= (double) value;
return *this;
}

Remember in the above examples, we barely modify our templates to achieve our goals.

Template classes

Templates and container classes

Combine IntArray & DoubleArray classes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
template <class T> // This is a template class, the user will provide the data type for T
class Array {
private:
int m_length;
T *m_data;
public:
Array() {
m_length = 0;
m_data = nullptr;
}
Array(int length) {
m_data = new T[length];
m_length = length;
}
~Array() {
delete[] m_data;
}
void Erase() {
delete[] m_data;
m_data = nullptr;
m_length = 0;
}
T& operator[] (int index) {
assert(index >= 0 && index < m_length);
return m_data[index];
}
int getLength(); // templated getLength() function on defined below
};

template <typename T> // member functions defined outside the class need their own template declaration
int Array<T>::getLength() { return m_length; } // note class name is Array<T>, not Array

Note that we’ve also defined the getLength() function outside of the class declaration. This isn’t necessary, but new programmers typically stumble when trying to do this for the first time due to the syntax, so an example is instructive. Each templated member function declared outside the class declaration needs its own template declaration. Also, note that the name of the templated array class is Array, not Array — Array would refer to a non-templated version of a class named Array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// short example
#include "Array.h"
int main() {
Array<int> intArray(12);
Array<double> doubleArray(12);

for (int count = 0; count < intArray.getLength(); ++count) {
intArray[count] = count;
doubleArray[count] = count + 0.5;
}

for (int count = intArray.getLength() - 1; count >= 0; --count) {
std::cout << intArray[count] << "\t" << doubleArray[count] << "\n";
}

return 0;
}

Template classes are instanced in the same way template functions are — the compiler stencils out a copy upon demand, with the template parameter replaced by the actual data type the user needs, and then compiles the copy. If you don’t ever use a template class, the compiler won’t even compile it.

Template classes are ideal for implementing container classes, because it is highly desirable to have containers work across a wide variety of data types, and templates allow you to do so without duplicating code. Although the syntax is ugly, and the error messages can be cryptic, template classes are truly one of C++’s best and most useful features.

Splitting up template classes

A template is not a class or a function -- it is a stencil used to create classes or functions. As such, it does not work in quite the same way as normal functions or classes. In most cases, this isn’t much of a issue. However, there is one area that commonly causes problems for developers.

With non-template classes, the common procedure is to put the class definition in a header file, and the member function definitions in a similarly named code file. In this way, the source for the class is compiled as a separate project file. However, with templates, this does not work. Consider this example:

Array.h:

1
2
3
4
5
6
7
8
9
10
template <class T>
class Array {
// ...
T& operator[] (int index) {
assert(index >= 0 && index < m_length);
return m_data[index];
}
int getLength();
// ...
};

Array.cpp:

1
2
3
4
#include "Array.h"

template <typename T>
int Array<T>::getLength() { return m_length; }

main.cpp:

1
2
Array<int> intArray(12);
Array<double> indoubleArray(12);

The above program will compile, but cause a linker error:

1
unresolved external symbol "public: int __thiscall Array::getLength(void)" (?GetLength@?$Array@H@@QAEHXZ)

In order for the compiler to use a template, it must see both the template definition (not just a declaration) and the template type used to instantiate the template.

Also remember that C++ compiles files individually. When the Array.h header is #included in main, the template class definition is copied into main.cpp. When the compiler sees that we need two template instances, Array, and Array, it will instantiate these, and compile them as part of main.cpp. However, when it gets around to compiling Array.cpp separately, it will have forgotten that we need an Array and Array, so that template function is never instantiated. Thus, we get a linker error, because the compiler can’t find a definition for Array::getLength() or Array::getLength().

The easiest way is to simply put all of your template class code in the header file (in this case, put the contents of Array.cpp into Array.h, below the class). In this way, when you #include the header, all of the template code will be in one place. The upside of this solution is that it is simple. The downside here is that if the template class is used in many places, you will end up with many local copies of the template class, which can increase your compile and link times (your linker should remove the duplicate definitions, so it shouldn’t bloat your executable). This is our preferred solution unless the compile or link times start to become a problem.

If you feel that putting the Array.cpp code into the Array.h header makes the header too long/messy, an alternative is to rename Array.cpp to Array.inl (.inl stands for inline), and then include Array.inl from the bottom of the Array.h header. That yields the same result as putting all the code in the header, but helps keep things a little cleaner.

Other solutions involve #including .cpp files, but we don’t recommend these because of the non-standard usage of #include.

Another alternative is to use a three-file approach. The template class definition goes in the header. The template class member functions goes in the code file. Then you add a third file, which contains all of the instantiated classes you need:

templates.cpp:

1
2
3
4
5
6
7
8
9
10
// Ensure the full Array template definition can be seen
#include "Array.h"
#include "Array.cpp" // we're breaking best practices here, but only in this one place

// #include other .h and .cpp template definitions you need here

template class Array<int>; // Explicitly instantiate template Array<int>
template class Array<double>; // Explicitly instantiate template Array<double>

// instantiate other templates here

The template class command causes the compiler to explicitly instantiate the template class. In the above case, the compiler will stencil out both Array and Array inside of templates.cpp. Because templates.cpp is inside our project, this will then be compiled. These functions can then be linked to from elsewhere.

Template non-type parameters

In previous lessons, you’ve learned how to use template type parameters to create functions and classes that are type independent. However, template type parameters are not the only type of template parameters available. Template classes and functions can make use of another kind of template parameter known as a non-type parameter.

Non-type parameters

A template non-type parameter is a special type of parameter that does not substitute for a type, but is instead replaced by a value. A non-type parameter can be any of the following:

  • A value that has an integral type or enumeration
  • A pointer or reference to a class object
  • A pointer or reference to a function
  • A pointer or reference to a class member function
  • std::nullptr_t

In the following example, we create a non-dynamic (static) array class that uses both a type parameter and a non-type parameter. The type parameter controls the data type of the static array, and the non-type parameter controls how large the static array is.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <class T, int size> // size is the non-type parameter
class StaticArray {
private:
T m_array[size];
public:
T* getArray();
T& operator[] (int index) {
return m_array[index];
}
};

// Showing how a function for a class with a non-type parameter is defined outside of the class
template <class T, int size>
T* StaticArray<T, size>::getArray() {
return m_array;
}

int main() {
StaticArray<double, 4> doubleArray;
// foo
return 0;
}

One noteworthy thing about the above example is that we do not have to dynamically allocate the m_array member variable! This is because for any given instance of the StaticArray class, size is actually constant. For example, if you instantiate a StaticArray, the compiler replaces size with 12. Thus m_array is of type int[12], which can be allocated statically. (compile time)

This functionality is used by the standard library class std::array. When you allocate a std::array, the int is a type parameter, and the 5 is a non-type parameter!

Function template specialization

When instantiating a function template for a given type, the compiler stencils out a copy of the templated function and replaces the template type parameters with the actual types used in the variable declaration. This means a particular function will have the same implementation details for each instanced type (just using different types). While most of the time, this is exactly what you want, occasionally there are cases where it is useful to implement a templated function slightly different for a specific data type.

Template specialization is one way to accomplish this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template <class T>
class Storage {
private:
T m_value;
public:
Storage(T value) {
m_value = value;
}
~Storage() {}

void print() {
std::cout << m_value << "\n";
}
};

int main() {
// Define some storage units
Storage<int> nValue(5);
Storage<double> dValue(6.7);
// Print out some values
nValue.print(); // 5
dValue.print(); // 6.7

return 0;
}

Now, let’s say we want double values (and only double values) to output in scientific notation. To do so, we can use a function template specialization (sometimes called a full or explicit function template specialization) to create a specialized version of the print() function for type double. This is extremely simple: simply define the specialized function (if the function is a member function, do so outside of the class definition), replacing the template type with the specific type you wish to redefine the function for. Here is our specialized print() function for doubles:

1
2
3
4
5
template <>
void Storage<double>::print() {
std::cout << std::scientific << m_value << "\n";
// 6.700000e+000
}

When the compiler goes to instantiate Storage::print(), it will see we’ve already explicitly defined that function, and it will use the one we’ve defined instead of stenciling out a version from the generic templated class.

The template <> tells the compiler that this is a template function, but that there are no template parameters (since in this case, we’re explicitly specifying all of the types). Some compilers may allow you to omit this, but it’s proper to include it.

Another example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
// Dynamically allocate a temporary string
char *str = new char[40];

// Ask user for their name
std::cout << "Enter your name: ";
std::cin >> string;

// Store the name
Storage<char*> storage(str);

delete[] string;

return 0;
}

As it turns out, instead of printing the name the user input, storage.print() prints garbage! What’s going on here?

When Storage is instantiated for type char, the constructor for Storage<char> looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
template <>
Storage<char *>::Storage(char *value) {
m_value = value;
}

// fixed
template <>
Storage<char *>::Storage(char *valueP) {
int length = 0;
while (valueP[length] != '\0')
++length;
++length; // +1 to account for null terminator;

// Allocate memory to hold the valueP string
m_value = new char[length];

// copy
for (int count = 0; count < length; ++count) {
m_value[count] = valueP[count];
}
}
// we need to add destructor to avoid memory leak
template <>
Storage<char *>::~Storage() {
delete[] m_value; // use delete[] to correspond new[]
}

Class template specialization

It is not only possible to specialize functions, it is also possible to specialize an entire class!

Consider the case where you want to design a class that stores 8 objects. Here’s a simplified class to do so:

1
2
3
4
5
6
7
8
9
10
11
12
template <class T>
class Storage8 {
private:
T m_array[8];
public:
void set(int index, const T &value) {
m_array[index] = value;
}
const T& get(int index) {
return m_array[index];
}
};

Consider when we use it to store bool:

1
Storage8<bool> boolStorage;

A variable of type bool ends up using an entire byte even though technically it only needs a single bit to store its true or false value! Thus, a bool is 1 bit of useful information and 7 bits of wasted space. Our Storage8 class, which contains 8 bools, is 1 byte worth of useful information and 7 bytes of wasted space.

As it turns out, using some basic bit logic, it’s possible to compress all 8 bools into a single byte, eliminating the wasted space altogether. However, in order to do this, we’ll need to revamp the class when used with type bool, replacing the array of 8 bools with a variable that is a single byte in size. While we could create an entirely new class to do so, this has one major downside: we have to give it a different name. Then the programmer has to remember that Storage8<T> is meant for non-bool types, whereas Storage8Bool (or whatever we name the new class) is meant for bools. That's needless complexity we'd rather avoid. Fortunately, C++ provides us a better method: class template specialization.

Class template specialization

Class template specializations are treated as completely independent classes, even though they are allocated in the same way as the templated class. This means that we can change anything and everything about our specialization class, including the way it’s implemented and even the functions it makes public, just as if it were an independent class. Here’s our specialized class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <>
class Storage8<bool> { // we're specializing Storage8 for bool
// What follows is just standard class implementation
private:
unsigned char m_data;
public:
Storage8() : m_data(0) {}
void set(int index, bool value) {
unsigned char mask = 1 << index;
if (value)
m_data |= mask;
// use bitwise-or to turn that bit on
else
m_data &= ~mask;
// use bitwise-and the inverse mask to turn that bit off
}
bool get(int index) {
unsigned char mask = 1 << index;
return (m_data & mask);
}
};

Note that we start off with template<>. The template keyword tells the compiler that what follows is templated, and the empty angle braces means that there aren’t any template parameters. In this case, there aren’t any template parameters because we’re replacing the only template parameter (typename T) with a specific type (bool).

Now, when we declare a class of type Storage8, where T is not a bool, we’ll get a version stenciled from the generic templated Storage8 class. When we declare a class of type Storage8, we’ll get the specialized version we just created. Note that we have kept the publicly exposed interface of both classes the same — while C++ gives us free reign to add, remove, or change functions of Storage8 as we see fit, keeping a consistent interface means the programmer can use either class in exactly the same manner.

Partial template specialization

Let’s take another look at the Static Array class we used in one of our previous examples:

1
2
3
4
5
6
7
8
9
10
template <class T, int size> // type parameter & expression parameter
class StaticArray {
private:
T m_array[size]
public:
T* getArray() { return m_array; }
T& operator[] (int index) {
return m_array[index];
}
};

Now, let’s say we wanted to write a function to print out the whole array. Although we could implement this as a member function, we’re going to do it as a non-member function instead because it will make the successive examples easier to follow.

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename T, int size>
void print(StaticArray<T, size> &array) {
for (int count = 0; count < size; ++count) {
std::cout << array[count] << ' ';
}
}

// so in main we can do:
StaticArray<int, 4> int4;
print(int4);
/*
output: 0 1 2 3
*/

Although this works, it has a design flaw. Consider the following:

1
2
3
4
5
6
StaticArray<char, 14> char14;
strcpy_s(char14.getArray(), 14, "Hello World!");
print(char14);
/*
output: H e l l o , w o r l d !
*/

For non-char types, it makes sense to put a space between each array element, so they don’t run together. However, with a char type, it makes more sense to print everything run together as a C-style string, which our print() function doesn’t do.

One might first think of using template specialization. The problem with full template specialization is that all template parameters must be explicitly defined.

1
2
3
4
5
6
7
8
9
10
template <typename T, int size>
void print(StaticArray<T, size> &array) {
// for-loop
}

// Override print() for fully specialized StaticArray<char, 14>
template <>
void print(StaticArray<char, 14> &array) {
// for-loop, but not printing ' '
}

Although this solves the issue of making sure print() can be called with a StaticArray, it brings up another problem: using full template specialization means we have to explicitly define the length of the array this function will accept! Consider the following example:

1
2
3
StaticArray<char, 12> char12;
strcpy_s(char12.getArray(), 12, "Hello, mom!");
print(char12);

Calling print() with char12 will call the version of print() that takes a StaticArray, because char12 is of type StaticArray, and our overloaded print() will only be called when passed a StaticArray.

Although we could make a copy of print() that handles StaticArray, what happens when we want to call print() with an array size of 5, or 22? We’d have to copy the function for each different array size. That’s redundant.

Obviously full template specialization is too restrictive a solution here. The solution we are looking for is partial template specialization.

Partial template specialization

Partial template specialization allows us to specialize classes (but not individual functions!) where some, but not all, of the template parameters have been explicitly defined. For our challenge above, the ideal solution would be to have our overloaded print function work with StaticArray of type char, but leave the length expression parameter templated so it can vary as needed. Partial template specialization allows us to do just that!

1
2
3
4
5
template <int size>
void print(StaticArray<char, size> &array) {
for (int count = 0; count < size; ++count)
std::cout << array[count];
}

As you can see here, we’ve explicitly declared that this function will only work for StaticArray of type char, but size is still a templated expression parameter, so it will work for char arrays of any size. That’s all there is to it!

Again, print is not a member function here.

Note that as of C++14, partial template specialization can only be used with classes, not template functions (functions must be fully specialized). Our void print(StaticArray &array) example works because the print function is not partially specialized (it’s just an overloaded function using a class parameter that’s partially specialized).

Here’s a full program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <cstring>

template <class T, int size>
class StaticArray {
private:
T m_array[size];
public:
T* getArray() { return m_array; }
T& operator[] (int index) {
return m_array[index];
}
};

template <typename T, int size>
void print(StaticArray<T, size> &array) {
// for-loop (' ')
}

/*
template <> this is fully specialized template
// ------
void print(StaticArray<char, 14> &array) {
// for-loop (no ' ')
}
*/

// overloaded of print() function for partially specialized StaticArray<char, size>
// ------
template <int size> // we just specialize T
void print(StaticArray<char, size> &array) {
// for-loop (no ' ')
}

Partial template specialization for member functions

The limitation on the partial specialization of functions can lead to some challenges when dealing with member functions. For example, what if we had defined StaticArray like this?

1
2
3
4
5
6
public:
void print() {
for (int i = 0; i < size; ++i)
std::cout << m_array[i] << ' ';
std::cout << "\n";
}

Partially specialize print():

1
2
3
4
5
// Doesn't work
template <int size>
void StaticArray<double, size>::print() {
// for-loop
}

So how do we get around this? One obvious way is to partially specialize the entire class:

1
2
3
4
5
6
7
8
template <class T, int size>
class StaticArray {

};
template <int size>
class StaticArray<double, size> {
// foo
};

While it works, this isn’t a great solution, because we had to duplicate a lot of code from StaticArray to StaticArray.

If only there were some way to reuse the code in StaticArray in StaticArray. Sounds like a job for inheritance!

You might start off trying to write that code like this:

1
2
3
template <int size> // size is the expression parameter
class StaticArray<double, size>: public StaticArray< // Then what?
// But we can't reference StaticArray

Fortunately, there’s a workaround, by using a common base class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template <class T, int size>
class StaticArray_Base {
protected:
T m_array[size];
// foo
virtual void print() {
// foo
}
};

template <class T, int size>
class StaticArray : public StaticArray_Base<T, size> {
public:
StaticArray() {}
};

template <int size>
class StaticArray<double, size> : public StaticArray_Base<double, size> {
public:
virtual void print() override {
// foo
// this->m_array, the prefix "this->" is needed
// This is because the template parent of a template class is not instantiated during the compilation pass that first examines the template.
// These names appear to be non-dependent on the particular template instantiation, and therefore the definitions need to be available. (If you never look at the definition of arrayListType, then reading the code of unorderedArrayListType it would appear the list and length need to be some sort of globals.)
// One way, using this-> before all the inherited names: this->list, this->length.
}
};

This prints the same as above, but has significantly less duplicated code.

Partial template specialization for pointers

1
2
3
4
5
6
7
8
9
10
11
12
13
template <class T>
class Storage {
private:
T m_value;
public:
Storage(T value) {
m_value = value;
}
~Storage() {}
void print() {
std::cout << m_value << '\n';
}
};

We showed that this class had problems when template parameter T was of type char* because of the shallow copy/pointer assignment that takes place in the constructor. In that lesson, we used full template specialization to create a specialized version of the Storage constructor for type char that allocated memory and created an actual deep copy of m_value. For reference, here’s the fully specialized char Storage constructor and destructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <> // fully specialized
Storage<char *>::Storage(char* value) {
int length = 0;
while (value[length] != '\0')
++length;
++length; // +1 to account for null terminator

m_value = new char[length];

for (int count = 0; count < length; ++count)
m_value[count] = value[count];
}

template<> // fully specialized
Storage<char *>::~Storage() {
delete[] m_value;
}

While that worked great for Storage, what about other pointer types (such as int*)? It’s fairly easy to see that if T is any pointer type, then we run into the problem of the constructor doing a pointer assignment instead of making an actual deep copy of the element being pointed to.

Because full template specialization forces us to fully resolve templated types, in order to fix this issue we’d have to define a new specialized constructor (and destructor) for each and every pointer type we wanted to use Storage with! This leads to lots of duplicate code, which as you well know by now is something we want to avoid as much as possible.

Fortunately, partial template specialization offers us a convenient solution. In this case, we’ll use class partial template specialization to define a special version of the Storage class that works for pointer values. This class is considered partially specialized because we’re telling the compiler that it’s only for use with pointer types, even though we haven’t specified the underlying type exactly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <typename T>
class Storage<T*> { // this is a partial-specialization of Storage that works with pointer types
private:
T* m_value;
public:
Storage(T* value) { // for pointer btype T
// For pointers, we'll do a deep copy
m_value = new T(*value); // this copies a single value, not an array
}
~Storage() {
delete m_value; // so we use scalar delete here, not array delete
}
void print() {
std::cout << *m_value << "\n";
}
};
// main
int x = 7;
Storage<int *> myintptr(&x);
myintptr.print();

When myintptr is defined with an int* template parameter, the compiler sees that we have defined a partially specialized template class that works with any pointer type, and instantiates a version of Storage using that template.

It’s worth noting that because this partially specialized Storage class only allocates a single value, for C-style strings, only the first character will be copied. If the desire is to copy entire strings, a specialization of the constructor (and destructor) for type char can be fully specialized. The fully specialized version will take precedence over the partially specialized version. Here’s an example program that uses both partial specialization for pointers, and full specialization for char:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
template <class T>
class Storage {
private:
T m_value;
public:
Storage(T value) {
m_value = value;
}
~Storage() {}
void print() {
std::cout << m_value << "\n";
}
};

// Partial-specialization of Storage class for pointers
template <class T>
class Storage<T*> {
private:
T* m_value;
public:
Storage(T* value) {
m_value = new T(*value);
}
~Storage() {
delete m_value;
}
void print() {
std::cout << *m_value << "\n";
}
};

// ------------ Full specialization for type char * ----------

// Full specialization of constructor for type char *
template <>
Storage<char *>::Storage(char *value) {
int length = 0;
while (value[length] != '\0')
++length;
++length;
m_value = new char[length];
for (int count = 0; count < length; ++count)
m_value[count] = value[count];
}

// Full specialization of destructor for type char *
template <>
Storage<char*>::~Storage() {
delete[] m_value;
}

// Full specialization of print function for type char *
// Without this, printing a Storage<char*> would call Storage<T*>::print(), which only prints the first elemetn
template <>
void Storage<char*>::print() {
std::cout << m_value;
}

int main() {
Storage<int> myint(5);
myint.print();

int x = 7;
Storage<int *> myintptr(&x);

x = 9;
myintptr.print();

char *name = new char[40]{ "Alex" }; // requires C++14
// otherwise, use:
// char *name = new char[40];
// strcpy(name, "Alex");

// Store the name
Storage<char *> myname(name);

delete[] name;

myname.print();

return 0;
}

Using partial template class specialization to create separate pointer and non-pointer implementations of a class is extremely useful when you want a class to handle both differently, but in a way that’s completely transparent to the end-user.

0%