#ifdef UNIT_TESTING
#include <iostream>
#include <sstream>
#define SHOW_(expect, ...)\
do {\
std::ostringstream result;\
result.copyfmt(std::cout);\
result << (__VA_ARGS__);\
std::cout << __FUNCTION__ << ':' << __LINE__ << '\t'\
<< #__VA_ARGS__ << " --> " << result.str();\
if (result.str() != expect) {\
std::cout << " != " << expect;\
}\
std::cout << std::endl;\
}\
while (0)
#include "UpDownCounter.h"
void single_counter_constructors() {
SHOW_("3", UpDownCounter{3, 5, nullptr}.GetValue());
SHOW_("5", UpDownCounter{3, 5, nullptr}.GetLimit());
// SHOW_("3", UpDownCounter{3, 5, NULL}.GetValue());
// SHOW_("5", UpDownCounter{3, 5, NULL}.GetLimit());
SHOW_("3", UpDownCounter{3, 5}.GetValue());
SHOW_("5", UpDownCounter{3, 5}.GetLimit());
SHOW_("0", UpDownCounter{5, nullptr}.GetValue());
SHOW_("5", UpDownCounter{5, nullptr}.GetLimit());
// SHOW_("0", UpDownCounter{5, NULL}.GetValue());
// SHOW_("5", UpDownCounter{5, NULL}.GetLimit());
SHOW_("0", UpDownCounter{5}.GetValue());
SHOW_("5", UpDownCounter{5}.GetLimit());
SHOW_("0", UpDownCounter{}.GetValue());
SHOW_("0", UpDownCounter{}.GetLimit());
}
void chained_counter_constructors() {
UpDownCounter hi{0, 3}; SHOW_("0", hi.GetValue());
SHOW_("3", hi.GetLimit());
UpDownCounter lo{&hi}; SHOW_("0", lo.GetValue());
SHOW_("0", lo.GetLimit());
SHOW_("2", lo.DownCount(), hi.GetValue());
SHOW_("0", lo.UpCount(), hi.GetValue());
// !!!
// TBD: Test other combinations with chained counters
// !!!
}
#include <limits>
void single_counter_setter() {
UpDownCounter ud_0_max{}; SHOW_("0", ud_0_max.GetValue());
SHOW_("0", ud_0_max.GetLimit());
SHOW_("123", ud_0_max.SetValue(123), ud_0_max.GetValue());
SHOW_("0", ud_0_max.GetLimit());
}
#include <string>
void single_counter_max_wrap() {
const auto max = std::numeric_limits<UpDownCounter::value_type>::max();
auto max_init = [](auto m) {
std::ostringstream os;
os << m;
return os.str();
};
const auto almost_max = max_init(max-1);
const auto exactly_max = max_init(max+0);
UpDownCounter ud_0_max{}; SHOW_("0", ud_0_max.GetValue());
SHOW_("0", ud_0_max.GetLimit());
SHOW_(almost_max, ud_0_max.SetValue(max-1), ud_0_max.GetValue());
SHOW_(exactly_max, ud_0_max.UpCount(), ud_0_max.GetValue());
SHOW_("0", ud_0_max.UpCount(), ud_0_max.GetValue());
SHOW_("1", ud_0_max.UpCount(), ud_0_max.GetValue());
SHOW_("0", ud_0_max.DownCount(), ud_0_max.GetValue());
SHOW_(exactly_max, ud_0_max.DownCount(), ud_0_max.GetValue());
SHOW_(almost_max, ud_0_max.DownCount(), ud_0_max.GetValue());
}
void single_counter_oflow() {
UpDownCounter ud_3_5{3, 5}; SHOW_("3", ud_3_5.GetValue());
SHOW_("5", ud_3_5.GetLimit());
SHOW_("4", ud_3_5.UpCount(), ud_3_5.GetValue());
SHOW_("5", ud_3_5.GetLimit());
SHOW_("0", ud_3_5.UpCount(), ud_3_5.GetValue());
SHOW_("5", ud_3_5.GetLimit());
SHOW_("1", ud_3_5.UpCount(), ud_3_5.GetValue());
SHOW_("5", ud_3_5.GetLimit());
SHOW_("0", ud_3_5.DownCount(), ud_3_5.GetValue());
SHOW_("5", ud_3_5.GetLimit());
SHOW_("4", ud_3_5.DownCount(), ud_3_5.GetValue());
SHOW_("5", ud_3_5.GetLimit());
SHOW_("3", ud_3_5.DownCount(), ud_3_5.GetValue());
SHOW_("5", ud_3_5.GetLimit());
}
void chained_counters_oflow() {
UpDownCounter hi{3}; SHOW_("0", hi.GetValue());
SHOW_("3", hi.GetLimit());
UpDownCounter lo{4, &hi}; SHOW_("0", lo.GetValue());
SHOW_("4", hi.GetLimit());
SHOW_("1", lo.UpCount(), lo.GetValue());
SHOW_("0", hi.GetValue());
SHOW_("2", lo.UpCount(), lo.GetValue());
SHOW_("0", hi.GetValue());
SHOW_("3", lo.UpCount(), lo.GetValue());
SHOW_("0", hi.GetValue());
SHOW_("0", lo.UpCount(), lo.GetValue());
SHOW_("1", hi.GetValue());
SHOW_("1", lo.UpCount(), lo.GetValue());
SHOW_("1", hi.GetValue());
SHOW_("0", lo.DownCount(), lo.GetValue());
SHOW_("1", hi.GetValue());
SHOW_("3", lo.DownCount(), lo.GetValue());
SHOW_("0", hi.GetValue());
}
void counter_tests() {
single_counter_constructors();
chained_counter_constructors();
single_counter_setter();
single_counter_max_wrap();
single_counter_oflow();
chained_counters_oflow();
}
#include "Clock.h"
void clock_tick_tests() {
Clock c{"myclock"};
SHOW_("myclock=0.00:00:00", c);
SHOW_("true", c.IsFloor());
SHOW_("myclock=0.00:00:01", c.TickUp());
SHOW_("false", c.IsFloor());
SHOW_("myclock=0.00:00:02", c.TickUp());
SHOW_("myclock=0.00:00:03", c.TickUp());
SHOW_("myclock=0.00:00:58", c.Seconds(58));
SHOW_("myclock=0.00:00:59", c.TickUp());
SHOW_("myclock=0.00:01:00", c.TickUp());
SHOW_("false", c.IsFloor());
SHOW_("myclock=0.00:01:01", c.TickUp());
SHOW_("myclock=0.00:59:59", c.Minutes(59).Seconds(59));
SHOW_("myclock=0.01:00:00", c.TickUp());
SHOW_("false", c.IsFloor());
SHOW_("myclock=0.23:59:59", c.Hours(23).Minutes(59).Seconds(59));
SHOW_("myclock=1.00:00:00", c.TickUp());
SHOW_("false", c.IsFloor());
SHOW_("myclock=123.00:00:00", c.Days(123));
SHOW_("myclock=122.23:59:59", c.TickDown());
SHOW_("myclock=122.23:00:01", c.Minutes().Seconds(1));
SHOW_("myclock=122.23:00:00", c.TickDown());
SHOW_("myclock=122.22:59:59", c.TickDown());
SHOW_("myclock=122.00:00:00", c.Hours().Minutes().Seconds());
SHOW_("false", c.IsFloor());
SHOW_("myclock=0.00:00:00", c.Days());
SHOW_("true", c.IsFloor());
}
int main() {
std::cout.setf(std::ios::boolalpha);
counter_tests();
clock_tick_tests();
}
#else
#include <cstdlib>
#include <stdexcept>
#include <iostream>
extern void appl();
int main()
{
try {
appl();
return EXIT_SUCCESS;
}
catch (const char* e) {
std::clog << "terminated -- reason: " << e << std::endl;
}
return EXIT_FAILURE;
}
#endif
#ifndef ICLOCK_H
#define ICLOCK_H
#include <iosfwd>
class IClock {
public:
virtual ~IClock() =default;
virtual void Print(std::ostream&) const =0;
virtual bool IsCeiling() const =0;
virtual bool IsFloor() const =0;
virtual IClock& TickUp() =0;
virtual IClock& TickDown() =0;
};
inline
std::ostream& operator<<(std::ostream& lhs, const IClock& rhs) {
rhs.Print(lhs);
return lhs;
}
#endif
#ifndef CLOCK_H
#define CLOCK_H
#include <cstring>
#include <iosfwd>
#include "IClock.h"
#include "UpDownCounter.h"
class Clock : public IClock {
const char* name_;
UpDownCounter days_;
UpDownCounter hours_;
UpDownCounter minutes_;
UpDownCounter seconds_;
public:
Clock(const char* name,
UpDownCounter::value_type days = 0,
UpDownCounter::value_type hours = 0,
UpDownCounter::value_type minutes = 0,
UpDownCounter::value_type seconds = 0)
: name_{std::strcpy(new char[std::strlen(name)+1], name)}
, days_{days, 0, nullptr}
, hours_{hours, 24, &days_}
, minutes_{minutes, 60, &hours_}
, seconds_{seconds, 60, &minutes_}
{}
~Clock() { delete[] name_; } // REQUIRED to return heap memory
Clock(const Clock&) =delete; // NO copy c'tor
Clock& operator=(const Clock&) =delete; // NO copy assignment
Clock(Clock&&) =delete; // NO move c'tor
Clock& operator=(Clock&&) =delete; // NO move assignment
Clock& Days(unsigned v = 0)
{ days_.SetValue(v); return *this; }
Clock& Hours(unsigned v = 0)
{ hours_.SetValue(v); return *this; }
Clock& Minutes(unsigned v = 0)
{ minutes_.SetValue(v); return *this; }
Clock& Seconds(unsigned v = 0)
{ seconds_.SetValue(v); return *this; }
virtual void Print(std::ostream&) const override final;
virtual bool IsCeiling() const override final;
virtual bool IsFloor() const override final;
virtual IClock& TickUp() override final
{ if (!IsCeiling()) seconds_.UpCount(); return *this; }
virtual IClock& TickDown() override final
{ if (!IsFloor()) seconds_.DownCount(); return *this; }
};
#endif
#include "Clock.h"
#include <iostream>
#include <iomanip>
bool Clock::IsCeiling() const {
return (seconds_.GetValue()+1 == 0)
&& (minutes_.GetValue()+1 == 0)
&& (hours_.GetValue()+1 == 0)
&& (days_.GetValue()+1 == 0);
}
bool Clock::IsFloor() const {
return (seconds_.GetValue() == 0)
&& (minutes_.GetValue() == 0)
&& (hours_.GetValue() == 0)
&& (days_.GetValue() == 0);
}
void Clock::Print(std::ostream& s) const {
std::ostream os{s.rdbuf()};
os.fill('0');
auto name = name_ ? name_ : "?dead?";
os << name << '=' << days_.GetValue() << '.'
<< std::setw(2) << hours_.GetValue() << ':'
<< std::setw(2) << minutes_.GetValue() << ':'
<< std::setw(2) << seconds_.GetValue();
}
#ifndef UP_DOWN_COUNTER_H
#define UP_DOWN_COUNTER_H
#include <cstdint>
#include <type_traits>
class UpDownCounter {
public:
using value_type = std::uint8_t;
static_assert(std::is_integral<value_type>::value
&& std::is_unsigned<value_type>::value,
"designed to work with unsigned integral types only");
private:
value_type value_{};
const value_type max_value_{};
UpDownCounter *next_counter_{};
public:
UpDownCounter() =default;
UpDownCounter(const UpDownCounter&) =delete;
UpDownCounter(UpDownCounter&&) =delete;
UpDownCounter& operator=(const UpDownCounter&) =delete;
UpDownCounter& operator=(UpDownCounter&&) =delete;
UpDownCounter(value_type value, value_type max_value, UpDownCounter* next_counter)
: value_{value}
, max_value_{max_value}
, next_counter_{next_counter}
{}
UpDownCounter(value_type max_value, UpDownCounter* next_counter)
: UpDownCounter(0, max_value, next_counter)
{}
UpDownCounter(value_type value, value_type max_value)
: UpDownCounter(value, max_value, nullptr)
{}
UpDownCounter(value_type max_value)
: UpDownCounter(0, max_value, nullptr)
{}
UpDownCounter(UpDownCounter* next_counter)
: UpDownCounter(0, 0, next_counter)
{}
void SetValue(value_type value) { value_ = value; }
int GetValue() const { return value_; }
int GetLimit() const { return max_value_; }
void UpCount();
void DownCount();
};
#endif
#include "UpDownCounter.h"
void UpDownCounter::UpCount() {
if (++value_ == max_value_) {
value_ = 0;
if (next_counter_) {
next_counter_->UpCount();
}
}
}
void UpDownCounter::DownCount() {
if (value_ == 0) {
value_ = max_value_;
if (next_counter_) {
next_counter_->DownCount();
}
}
--value_;
}
#include <iostream>
void appl() {
throw "not yet implemented";
}
A Clock That Can Tick Up And Down
---------------------------------
At this point a "fresh start" is made by building a `Clock` which
can count up and down.
The former are build based on objects of `UpDownCounter`-s, which
– as their name suggests – supports counting up and down too, not
only when used "stand alone" but also when "chained" together.
Intensive test code is supplied for both classes. (It may also
be useful to understand the intended use of both of the classes.)
To advance to the next step apply the following changes:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
STOP – FIRST do an thorough code review so that you at least can
answer the questions following below.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* Obviously there are two different `main` programs included in
`main.cpp` – how is it selected which one gets compiled?
* What is the difference between between the `SHOW` macro used
until now and the `SHOW_` macro with the underscore at the end
that is used here?
* Understand the purpose of the different constructors of class
`UpDownCounter`:
* Which ones are for building chains of counters?
* Which ones allow to build a counter that uses?
* What may be the reason for using a `static_assert` to enforce
an unsigned type for instantiating the `UpDownCounter`?
* What (currently) happens if you compile `main` so that it run
the code in `appl.cpp`?
-----------------------------------------------------------------
If you think you have understood enough of the code to do one
or more of the modifications suggested below feel free to try
your hands on it.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
As all suggestions are INDEPENDANT of each other you may well
READ THE WHOLE LIST FIRST and then "pick and choose" according to
the "General Topic" which is of most interest to you.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Alternatively go to the solutions and compare the source files
with tool of your choice shows the diff-s.
-----------------------------------------------------------------
* General Topic – Code Robustness
* In the constructor of class `UpDownCounter` make sure the
current `value_` is inside the range `0` and `max_value - 1`
(Proposal: If the value set is equal to or exceeds the
maximum value allowed set it to the maximum allowed value.
* Similar in `SetValue`, but in this case indicate via a
`bool` return value whether the new value has actually been
set (then return `true`) or was out of range and has not
been set (then return `false`).
* General Topic – Exceptions
* Modify the `main` program` calling the `appl` function
(defined in source file `appl.c`) so that it catches the
exception currently thrown and displays its text before
ending with `EXIT_FAILURE`.
* Try throwing the following different exceptions from `appl`
and demonstrate a `catch(...)` (i.e. using three dots as
argument list will handle them all:
* just an integral number as "error code";
* an exception class `NotYetImplememted` defined for exactly
that purpose;
* an appropriate class from the standard exceptions;
* In the `main` program calling `appl` in its `try` block, add
an add all the appropriate `catch` blocks to show a meaningful
message depending on which exception was thrown, i.e.
* if an integral value (as "error code") is thrown, include
its value in the message;
* if a `const char *` is throw simply display that;
* if a standard exception is thrown display its `what()`
text;
* if the most generic catch block (the one defined with the
three dots in its parenthesis) give a general message like
"application terminated with non-standard exception" (you
cannot do more than that as no information is portably
available in this case);
* If you throw an individually defined `NotYetImplemented`
exception demonstrate what happens if there is NO specific
`catch` block for it …
* … when this exception is actually derived from one of
standard exceptions;
* … and when this is NOT the case.
* General Topic – TDD (Test Driven Development)
* Modify the `SHOW_`-macro so that …
* … it generates output ONLY if the expectation is NOT met
* … it counts in two global variables how many times it has
been called and how many time the expectation was not met.
* At the end of the `main` program doing all the tests show a
message how tests have been run and how many of them failed
– if any, otherwise you may just show "N tests passed" (with
`N` being the total number of tests of course).
* General Topic – "Copy vs. Move"
* Consider the effort necessary to turn `Clock` from currently
being neither "copyable" nor "movable" into a "move only"
class (i.e. still not copyable but may be returned as result
from a "factory" function.
* Be sure to understand the potential problem with the
`UpDownCounter` chaining via pointers – the fact these
objects are NEITHER "copyable" NOR "movable" themselves has
an important reason!
* The solution is NOT to provide move-operations in the
`UpDownCounter` class – the context to do it correctly
is NOT available here!
* Rather the move-operations of class `Clock` needs to use
the appropriate constructors of `UpDownCounter` (which
already exists) to take the `value_` and `max_value_` from
the `Clock` object being MOVED FROM but build the chain in
the correct for the object being MOVED TO.
* Beyond that, don't forget that the `name_` (pointer member)
of class `Clock` would need to be moved too.
* If you feel you understood that all – GO FOR IT AND DO THE
IMPLEMENTATION …
* … and do not forget to ADD TESTS for it too
* (or maybe write the tests before the implementation, which
would be even more in the "spirit of TDD")
all: main run
run: main
./main
main: *.cpp *.h
g++ -std=c++14 *.cpp -o $@
unit_tests: *.cpp *.h
g++ -std=c++14 -DUNIT_TESTING *.cpp -o unit_tests
@./unit_tests
clean:
-rm -f *.o a.out core main unit_tests
zip: clean
zip ../clock-07.zip *.txt *.cpp *.h Makefile
.PHONY: all run clean zip