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_vecto afterint_vec. - The
int_vecclass 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_vecclass. All the method names start withint_vec::because they belong toint_vec. - Step 2: Put the
int_vecclass in a.hheader file, and put the method implementations in a
.cppfile. We’ll call these filesint_vec.handint_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.cppseparately, which is a big time-saver for large programs.Notice that
int_vec.hbegins with these two lines:#ifndef INT_VEC_H #define INT_VEC_H
And ends with this line:
#endifIn 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_Htests ifINT_VEC_Hhas been defined before (by a#definestatement). If so, then everything after#ifndef INT_VEC_His skipped and not seen by the compiler. But ifINT_VEC_His not defined, then it is immediately defined in the next line, and the rest of the file is compiled as usual. The#endifat the end of the file matches the#ifndefat 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.htwice or more in the same file, which will cause the compile to complain thatint_vecis being defined multiple times (a class must be defined exactly once in C++). The#ifndefacts as a guard to make sure that at most one instance ofint_vec.hgets compiled.- Step 3: Compile
int_vec.cppto create a.oobject 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.oshould 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
-cnear the end!- Step 4: As a test, create a
.cppfile with amainfunction 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
.oobject file using g++ and the-coption:$ g++ -c int_vec_test.cpp
Now
int_vec_test.oshould 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
.oobject files together using g++’s-ooption 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_testin 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
makefileso that all you need to do is typemaketo create theint_vec_testexecutable. 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();
}