Welcome to the Lab 13 study notes! This lab will explore two important C++ techniques for code organization and reuse: class composition and templates.
We will learn how to implement composition (the “has-a” relationship) by including objects of one class as members within another and understand the order of their construction and destruction.
Subsequently, we will delve into templates, particularly class templates, to see how to create generic classes usable with various data types. We will also discuss template specialization, including complete and partial specialization, and when to choose templates over inheritance.
Class composition refers to a class having members that are objects of another class. This relationship typically represents a “has-a” connection. For example, a “Car” class might “have an” “Engine” class object as one of its members.
Car(intn=4) : eng(n) { std::cout <<"Constructor:Car(int)\n"; } // Initialize member object via initializer list
16
voidstart() {
17
std::cout <<"car with "<< eng.getCylinder() <<" cylinder engine started\n";
18
eng.start();
19
}
20
~Car() { std::cout <<"Destructor:~Car()\n"; }
21
};
In the example above, the Car class contains an Engine object named eng.
1.2 Construction and Destruction Order of Member Objects#
Construction Order: When an object of the container class is created:
First, the constructors of the member objects are called in the order they are declared within the container class. Arguments for the member objects’ constructors are typically provided in the container class’s constructor initializer list.
Then, the body of the container class’s own constructor is executed.
Images
is-a
has-a
Output
Order
1.~derived class() 2.~base class()
1.~Car class() 2.~Enginec class()
Differ
Inheritance
Composition
Destruction Order: When an object of the container class is destroyed, the order is reversed:
First, the body of the container class’s own destructor is executed.
Then, the destructors of the member objects are called in the reverse order of their declaration within the container class.
Templates are a powerful feature in C++ that enable functions and classes to operate on generic types. This means they can work with a variety of data types without needing to be rewritten for each type.
2.1 Basic Concepts and Advantages of Class Templates#
Class templates allow us to define a generic class blueprint.
Advantages:
Code Reusability: Write code once for multiple types.
Type Safety: The compiler performs type checking at compile time.
Flexibility and Versatility: Easy to create general-purpose containers or algorithms applicable to different data structures.
2.2 Definition and Instantiation of Class Templates#
Class templates can accept not only types as parameters but also non-type parameters, such as integral constants, pointers, references, etc. These parameters must be compile-time constants.
Sometimes, a generic template definition may not be optimal or applicable for certain specific types. Template specialization allows us to provide a dedicated template implementation for a particular type or combination of types.
A specialized template class behaves like a new class; it does not inherit anything from the primary template (except the name). Any and all methods and members will have to be re-implemented.
When only some of the template parameters are specialized, or when a specialization is made for a characteristic of a parameter (like being a pointer or reference), it’s called partial specialization. The result of a partial specialization is still a template.
Case 1: Multiple type parameters, some specialized
1
// Primary template
2
template <typenameT1, typenameT2>
3
classData {
4
public:
5
Data(T1m, T2n) { /* ... */ }
6
voiddisplay() { /* ... */ }
7
};
8
9
// Partial specialization: when T2 is char
10
template <typenameT1>
11
classData<T1, char> {
12
public:
13
Data(T1m, charc) { /* ... */ }
14
voiddisplay() { /* ... */ }
15
};
Case 2: Partial specialization for pointer types
1
// Primary template
2
template <typenameT>
3
classBag {
4
T elem;
5
// ...
6
public:
7
voidadd(Tt) { /* ... */ }
8
};
9
10
// Partial specialization: when T is a pointer type T*
11
template <typenameT>
12
classBag<T*> {
13
T* elem; // Stores a pointer
14
// ...
15
public:
16
voidadd(T*t) { // Parameter is a pointer
17
// Could store the pointer itself, or dereference and store the value
18
// The slide example stores the dereferenced value
19
if (t !=nullptr) {
20
// elem[size++] = *t; // Assuming elem is an array of T
21
}
22
}
23
};
The Bag<T*> example from the slides demonstrates that if the template argument is T* (a pointer), the add method takes a T* and internally stores the value pointed to by dereferencing *t. Without partial specialization, only the pointers themselves would be added.
NOTE
假设我们有一个模板类用于存储两个值,现在希望为第二个参数为int时提供特殊实现:
1
#include<iostream>
2
#include<string>
3
4
template <typenameT, typenameU>
5
classPair {
6
public:
7
T first;
8
U second;
9
10
voidprint() {
11
std::cout << first <<", "<< second <<std::endl;
12
}
13
};
14
15
// 偏特化
16
template <typenameT>
17
classPair<T, int> {
18
public:
19
T first;
20
int second;
21
22
voidprint() {
23
std::cout << first <<" (int) "<< second <<std::endl;
24
}
25
};
26
27
intmain() {
28
Pair<std::string, double> p1{"Hello", 3.14};
29
p1.print(); // 调用普通模板类
30
31
Pair<std::string, int> p2{"World", 42};
32
p2.print(); // 调用偏特化版本
33
return0;
34
}
输出结果:
1
Hello, 3.14
2
World (int) 42
3.3 Differences Between Function Template Specialization and Class Template Specialization#
Function and class template specialization are powerful features in C++ that allow you to provide customized implementations for specific types. Here are the main differences and use cases:
Function and class template specialization are very useful in template programming, especially when dealing with special cases or optimizing performance. Using them wisely can enhance the flexibility and efficiency of your code, but it is important to avoid over-complicating the code structure.
Class templates and their member function templates should generally be declared and defined in header files (.h or .hpp). This is because the compiler needs to see the full definition of the template when it instantiates it.
Templates: Used to generate a set of classes where the object type does not affect the functional behavior of the class (i.e., the algorithm is generic, only the data type it operates on differs). Focuses on algorithmic generality.
Inheritance: Used for a set of classes where the object type does affect the functional behavior of the class (via virtual functions for polymorphism). Focuses on achieving different behaviors through a common interface.
The declarations of Point class and Line class are as follows. Implement the member functions of these two classes and then run the program to test the classes.
1
classPoint {
2
private:
3
double x, y;
4
public:
5
Point(doublenewX, doublenewY);
6
Point(constPoint&p);
7
doublegetX() const;
8
doublegetY() const;
9
};
10
11
classLine {
12
private:
13
Point p1, p2; // Composition: Line class contains two Point objects
14
double distance;
15
public:
16
Line(Pointxp1, Pointxp2);
17
Line(constLine&q);
18
doublegetDistance() const;
19
};
20
21
// main function test code
22
intmain() {
23
Point a(8, 9),b(1,2);
24
Point c = a;
25
// ... output coordinates of points a, b, c ...
26
Line line1(a, b);
27
// ... output distance of line1 ...
28
Line line2(line1);
29
// ... output distance of line2 ...
30
return0;
31
}
Hint: The constructor of Line needs to calculate the distance between the two points and store it in the distance member. Distance formula: sqrt((x2-x1)^2 + (y2-y1)^2).
A template class named Pair is defined as follows. Please implement the overloaded operator< which compares the value of the key. If this->key is smaller than p.key, return true. Then define a friend function to overload the << operator which displays the Pair’s data members. Finally, run the program. The output sample is as follows.
There is a definition of a template class Dictionary. Please write a template partial specialization for the Dictionary class where the Key is specified to be int. In this specialized version, add a member function named sort() which sorts the elements in the dictionary in ascending order (based on keys). Finally, run the program. The output sample is as follows.