On Friday we had a short debate in the room with the guys about the role of header and cpp files. I didn’t support to separate unit tests to header and implementation part, because I guess in this case it’s not necessary. Why?
Data types (i.e. classes) have abstract and concrete aspects. We store the abstract aspects in header files (using c++ this is the .h file) and the concrete aspects in implementation files (cpp file). In the header we define the data type’s signature, export interface (public part of a class), parameters, methods, properties. Using C this kind of approach is shown better because header files can be separate from the implementation well, while in C++ sometimes you must store implementation specific properties in the header (usually in the private space).
The more information we define in the header file in connection with the implementation, the harder to change the implementation later without changing the header file. It can be a problem in a complex and huge software, where changing something in a deeper layer can result more hours compilation time.
In my example I show you an implementation of the stack data type. Stack is a very simple type, we can push elements to it, pop these elements out and get the number of the stored elements. The stack can be implemented in different ways, we can use vector or linked list to make it work.
In this example I’m going to store integer value in it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #ifndef STACK__H #define STACK__H class Stack { public: Stack(const int size); ~Stack(); Stack& push(const int value); int pop(); int size() const; private: int *entries; int stack_size; }; #endif |
#ifndef STACK__H #define STACK__H class Stack { public: Stack(const int size); ~Stack(); Stack& push(const int value); int pop(); int size() const; private: int *entries; int stack_size; }; #endif
There’s a problem with this header file. Using int *entries assumes we want to use array to store values. However it’s good for shorter stacks, since handling larger stack is not so efficient, because we have to allocate in the memory the whole size of the stack at the beginning. Later, when you want to change it to linked list to it can be more efficient in memory consumption, you must recompile every part of your software that includes this header file.
Using another structure we can change very easy to different implementation without to compile the software again. Considering the next change:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #ifndef STACK__H #define STACK__H struct _stack_impl; class Stack { public: Stack(const int size); ~Stack(); Stack& push(const int value); int pop(); int size() const; private: _stack_impl *impl; int stack_size; }; #endif |
#ifndef STACK__H #define STACK__H struct _stack_impl; class Stack { public: Stack(const int size); ~Stack(); Stack& push(const int value); int pop(); int size() const; private: _stack_impl *impl; int stack_size; }; #endif
stack_impl structure gives opportunity to define properties in the cpp file. Stack_size is a property of the stack, so it can be in the header file.
In the cpp file that’s using linked list, the stack_impl looks like this:
1 2 3 4 5 6 7 8 9 10 11 | struct Entry { Entry *prev; int value; }; struct _stack_impl { Entry *first; Entry *top; int size; }; |
struct Entry { Entry *prev; int value; }; struct _stack_impl { Entry *first; Entry *top; int size; };
and with array:
1 2 3 4 5 6 7 8 9 | struct Entry { int value; }; struct _stack_impl { Entry *entries; int top; }; |
struct Entry { int value; }; struct _stack_impl { Entry *entries; int top; };
The implementation with linked list (stack.cpp) is:
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 | #include "stack.h" struct Entry { Entry *prev; int value; }; struct _stack_impl { Entry *first; Entry *top; int size; }; Stack::Stack(const int size) : stack_size(size), impl(new _stack_impl) { impl->first = 0; impl->top = 0; impl->size = 0; } Stack::~Stack() { delete impl; } Stack& Stack::push(const int value) { if (stack_size == impl->size) throw "FULL"; Entry *entry = new Entry; entry->value = value; entry->prev = impl->top; impl->top = entry; impl->size++; return *this; } int Stack::pop() { if (impl->size == 0) throw "EMPTY"; Entry *temp = impl->top; impl->top = impl->top->prev; int value = temp->value; delete temp; return value; } int Stack::size() const { return impl->size; } |
#include "stack.h" struct Entry { Entry *prev; int value; }; struct _stack_impl { Entry *first; Entry *top; int size; }; Stack::Stack(const int size) : stack_size(size), impl(new _stack_impl) { impl->first = 0; impl->top = 0; impl->size = 0; } Stack::~Stack() { delete impl; } Stack& Stack::push(const int value) { if (stack_size == impl->size) throw "FULL"; Entry *entry = new Entry; entry->value = value; entry->prev = impl->top; impl->top = entry; impl->size++; return *this; } int Stack::pop() { if (impl->size == 0) throw "EMPTY"; Entry *temp = impl->top; impl->top = impl->top->prev; int value = temp->value; delete temp; return value; } int Stack::size() const { return impl->size; }
And the implemantation with array (stack2.cpp)
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 | #include "stack.h" struct Entry { int value; }; struct _stack_impl { Entry *entries; int top; }; Stack::Stack(const int size) : stack_size(size), impl(new _stack_impl) { impl->entries = new Entry[size]; impl->top = -1; } Stack::~Stack() { delete[] impl->entries; } Stack& Stack::push(const int value) { if (impl->top+1 == stack_size) throw "FULL"; impl->top = impl->top+1; impl->entries[impl->top].value = value; return *this; } int Stack::pop() { if (impl->top == -1) throw "EMPTY"; return impl->entries[impl->top--].value; } int Stack::size() const { return impl->top+1; } |
#include "stack.h" struct Entry { int value; }; struct _stack_impl { Entry *entries; int top; }; Stack::Stack(const int size) : stack_size(size), impl(new _stack_impl) { impl->entries = new Entry[size]; impl->top = -1; } Stack::~Stack() { delete[] impl->entries; } Stack& Stack::push(const int value) { if (impl->top+1 == stack_size) throw "FULL"; impl->top = impl->top+1; impl->entries[impl->top].value = value; return *this; } int Stack::pop() { if (impl->top == -1) throw "EMPTY"; return impl->entries[impl->top--].value; } int Stack::size() const { return impl->top+1; }
Using this somewhere (let this be the main.cpp), we can call the stack’s methods:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include #include "stack.h" int main() { Stack stack(5); stack.push(2); stack.push(3); stack.push(4); printf("pop: %d\n", stack.pop()); printf("pop: %d\n", stack.pop()); printf("size: %d\n", stack.size()); return 0; } |
#include #include "stack.h" int main() { Stack stack(5); stack.push(2); stack.push(3); stack.push(4); printf("pop: %d\n", stack.pop()); printf("pop: %d\n", stack.pop()); printf("size: %d\n", stack.size()); return 0; }
Create the object files using g++:
1 2 3 | g++ -c main.cpp g++ -c stack.cpp g++ -c stack2.cpp |
g++ -c main.cpp g++ -c stack.cpp g++ -c stack2.cpp
Finally we create two different executable code:
1 2 | g++ stack.o main.o -o linked_list g++ stack2.o main.o -o array |
g++ stack.o main.o -o linked_list g++ stack2.o main.o -o array
So for unit testing I would create only a cpp file and put the class declaration and definition there inline, because it doesn’t have any implementation code that we want to hide. I guess the unit test be simple, perspicuous and brief, so guys, don’t separate it…
Leave Your Response