int_vec: A Header and an Implementation¶
The purpose of this note is to discuss the process of splitting a class, such
as int_vec
, into a .h
file and .cpp
. This usually what is done
with in large programs. It allows the code for int_vec
to be compiled
once, but #included as many times as needed.
- Step 1: Move all the method bodies of
int_vec
to afterint_vec
. - The
int_vec
class below shows the result of doing this. What remains in the class itself are just the (private) variables, and the headers for all the methods. The method bodies are declared after theint_vec
class. All the method names start withint_vec::
because they belong toint_vec
. - Step 2: Put the
int_vec
class in a.h
header file, and put the method implementations in a
.cpp
file. We’ll call these filesint_vec.h
andint_vec.cpp
. They have this basic structure:// int_vec.h class int_vec { // ... }; void clear(int_vec& v); // ... ////////////////////////////////////////////////////////////// // int_vec.h #ifndef INT_VEC_H #define INT_VEC_H #include "int_vec.h" #include "cmpt_error.h" #include <iostream> // ... // ... all functions and methods of int_vec go here ... #endif
Now we can compile
int_vec.cpp
separately, which is a big time-saver for large programs.Notice that
int_vec.h
begins with these two lines:#ifndef INT_VEC_H #define INT_VEC_H
And ends with this line:
#endif
In C++, commands that start with
#
are pre-processor directives, i.e. commands to the pre-processor that scans the code before the main C++ compiler sees it.The directive
#ifndef INT_VEC_H
tests ifINT_VEC_H
has been defined before (by a#define
statement). If so, then everything after#ifndef INT_VEC_H
is skipped and not seen by the compiler. But ifINT_VEC_H
is not defined, then it is immediately defined in the next line, and the rest of the file is compiled as usual. The#endif
at the end of the file matches the#ifndef
at the top.The reason for doing this is to prevent multiple inclusion of
int_vec.h
. In large programs, it is common to unintentionally include a file likeint_vec.h
twice or more in the same file, which will cause the compile to complain thatint_vec
is being defined multiple times (a class must be defined exactly once in C++). The#ifndef
acts as a guard to make sure that at most one instance ofint_vec.h
gets compiled.- Step 3: Compile
int_vec.cpp
to create a.o
object file that can later be linked into other files to create an executable program.
The basic command option to compile a file in g++ is
-c
, and is used like this:$ g++ -c int_vec3.cpp
After running this command successfully, the object file
int_vec.o
should be in the folder.We are using g++ with various options turned on, so the actual g++ command is longer:
g++ -std=c++11 -Wall -Wextra -Werror -Wfatal-errors -Wno-sign-compare -Wnon-virtual-dtor -g -c int_vec.cpp
Don’t miss the
-c
near the end!- Step 4: As a test, create a
.cpp
file with amain
function and some code that uses
int_vec
. Lets call thisint_vec_test.cpp
:// int_vec_test.cpp #include "int_vec.h" #include <iostream> using namespace std; int main() { int_vec a; for(int i = 0; i < 10; ++i) { a.append(i); } cout << "a = " << a << "\n"; clear(a); cout << "a = " << a << "\n"; }
We compile this into a
.o
object file using g++ and the-c
option:$ g++ -c int_vec_test.cpp
Now
int_vec_test.o
should be in the folder.As before, the actual g++ command has more options and would look like this:
$ g++ -std=c++11 -Wall -Wextra -Werror -Wfatal-errors -Wno-sign-compare -Wnon-virtual-dtor -g -c int_vec3_test.cpp
- Step 4: Link the
.o
object files together using g++’s-o
option to make an executable file:
g++ -o int_vec3_test int_vec3.o int_vec3_test.o
If all goes well, this creates an executable file named
int_vec_test
in the current directory that you can run like this:$ ./int_vec3_test
Separate compilation and the linking the way we’ve done it here has lots of little details you need to get right, and so in practice, these steps are usually written down in a
makefile
so that all you need to do is typemake
to create theint_vec_test
executable. However, in this course we won’t go any further into the details of usingmake
.
int_vec.h¶
// int_vec.h
#ifndef INT_VEC_H
#define INT_VEC_H
#include <iostream>
#include "cmpt_error.h"
#include <cassert>
using namespace std;
class int_vec {
private:
int capacity; // length of underlying array
int* arr; // pointer to the underlying array
int size; // # of elements in this int_vec from user's perspective
void resize(int new_cap);
public:
int_vec();
int_vec(int sz, int fill_value);
int_vec(const int_vec& other);
~int_vec();
int get_size() const;
int get_capacity() const;
int get(int i) const;
void set(int i, int x);
int& operator[](int i);
int operator[](int i) const;
void print() const;
void println() const;
void append(int x);
int_vec& operator=(const int_vec& other);
friend void clear(int_vec& v);
}; // class int_vec
void clear(int_vec& v);
bool operator==(const int_vec& a, const int_vec& b);
bool operator!=(const int_vec& a, const int_vec& b);
ostream& operator<<(ostream& out, const int_vec& arr);
#endif
int_vec.cpp¶
// int_vec.cpp
#include "int_vec.h"
void int_vec::resize(int new_cap) {
if (new_cap < capacity) return;
capacity = new_cap;
int* new_arr = new int[capacity]; // create new, bigger array
for(int i = 0; i < size; ++i) { // copy elements of arr
new_arr[i] = arr[i]; // into new_arr
}
delete[] arr; // delete old arr
arr = new_arr; // assign new_arr
}
int_vec::int_vec()
: capacity(10), arr(new int[capacity]), size(0)
{ }
int_vec::int_vec(int sz, int fill_value)
: capacity(10), size(sz)
{
if (size < 0) cmpt::error("can't construct int_vec of negative size");
if (size > 0) capacity += size;
arr = new int[capacity];
for(int i = 0; i < size; ++i) {
arr[i] = fill_value;
}
}
int_vec::int_vec(const int_vec& other)
: capacity(other.capacity), arr(new int[capacity]), size(other.size)
{
cout << "int_vec copy constructor called ...\n";
for(int i = 0; i < size; ++i) {
arr[i] = other.arr[i];
}
}
int_vec::~int_vec() {
cout << "... ~int_vec called\n";
delete[] arr;
}
int int_vec::get_size() const {
return size;
}
int int_vec::get_capacity() const {
return capacity;
}
int int_vec::get(int i) const {
if (i < 0 || i > size) cmpt::error("get: index out of bounds");
return arr[i];
}
void int_vec::set(int i, int x) {
if (i < 0 || i > size) cmpt::error("get: index out of bounds");
arr[i] = x;
}
int& int_vec::operator[](int i) {
cout << "(modifying operator[] called)\n";
return arr[i];
}
int int_vec::operator[](int i) const {
cout << "(const operator[] called)\n";
return arr[i];
}
void int_vec::print() const {
if (size == 0) {
cout << "{}";
} else {
cout << "{";
cout << arr[0];
for (int i = 1; i < size; ++i) { // i starts at 1 (not 0)
cout << ", " << arr[i];
}
cout << "}";
}
}
void int_vec::println() const {
print();
cout << "\n";
}
void int_vec::append(int x) {
if (size >= capacity) {
resize(2 * capacity); // double the capacity
}
assert(size < capacity);
arr[size] = x;
size++;
}
int_vec& int_vec::operator=(const int_vec& other) {
// self-assignment is a special case: don't need to do anything
if (this == &other) {
return *this;
} else {
// re-size this int_vecs underlying array if necessary
if (capacity < other.size) {
resize(other.size + 10); // a little bit of extra capacity
} // to speed up append
size = other.size;
for(int i = 0; i < size; ++i) { // copy other's values
arr[i] = other.arr[i]; // into this array
}
return *this;
}
}
////////////////////////////////////////////////////////////////////////////////
void clear(int_vec& v) {
v.size = 0;
}
bool operator==(const int_vec& a, const int_vec& b) {
if (a.get_size() != b.get_size()) {
return false;
} else {
for(int i = 0; i < a.get_size(); ++i) {
if (a.get(i) != b.get(i)) {
return false;
}
}
return true;
}
}
bool operator!=(const int_vec& a, const int_vec& b) {
return !(a == b);
}
ostream& operator<<(ostream& os, const int_vec& arr) {
if (arr.get_size() == 0) {
os << "{}";
} else {
os << "{";
os << arr.get(0);
for (int i = 1; i < arr.get_size(); ++i) { // i starts at 1 (not 0)
os << ", " << arr.get(i);
}
os << "}";
}
return os;
}
int_vec_test.cpp¶
// int_vec_test.cpp
#include "int_vec.h"
#include <iostream>
#include "cmpt_error.h"
void test() {
int_vec a;
for(int i = 0; i < 10; ++i) {
a.append(i);
}
cout << "a = " << a << "\n";
clear(a);
cout << "a = " << a << "\n";
}
int main() {
test();
}