I wanted to practice more object-oriented programming and decided to write a console based Quiz Application. In one of my previous question, a reviewer left some links on SOLID design pattern, I went through it and tried as much as possible to follow it to the best of my ability.
MAJOR CONCERNS
- I used boost library to serialize my object and I noticed my executable size increased quite a lot. I would appreciate if any one could give suggestion on how to save my objects without boost dependency, links would be preferable.
- I really considered removing the default constructor from my classes, but eventually left them since a container must be initialized if no default constructor is given. Does this show a poor class design?
- Some of my method functions were supposed to return a value for example
get()
, but in a scenario where an object of typeQuiz
was not found, I decided to throw an error. I recieved several warnings from g++ compiler, is it a poor design to throw, if no return value was produced in a function?
namespace QUIZMAKER.h
#ifndef QUIZ_QUIZMAKER_H_
#define QUIZ_QUIZMAKER_H_
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/vector.hpp>
namespace QuizMaker
{
struct Question
{
friend std::ostream& operator<<( std::ostream &os, const Question &question );
public:
Question() = default;
Question( const std::string &title, const std::string &question )
: title_{ title }, question_{ question } {}
std::string title_;
std::string question_;
private:
friend class boost::serialization::access;
template<typename Archive>
void serialize( Archive &archive, const unsigned int version )
{
archive & title_;
archive & question_;
}
};
struct QuestionOptions{
friend std::ostream& operator<<( std::ostream &os, const QuestionOptions &options );
public:
QuestionOptions() = default;
QuestionOptions( const std::vector<std::string> &options, const std::string &correct_option )
: options_{ options }, correct_option_{ correct_option } {}
std::vector<std::string> options_;
std::string correct_option_;
private:
friend class boost::serialization::access;
template<typename Archive>
void serialize( Archive &archive, const unsigned int version )
{
archive & options_;
archive & correct_option_;
}
};
struct TagContainer
{
friend std::ostream& operator<<( std::ostream &os, const TagContainer &tag_c );
TagContainer() = default;
explicit TagContainer( const std::vector<std::string> &tag_c ) : tag_container_{ tag_c } {}
std::vector<std::string> tag_container_;
private:
friend class boost::serialization::access;
template<typename Archive>
void serialize( Archive &archive, const unsigned int version )
{
archive & tag_container_;
}
};
struct Quiz
{
public:
Quiz() = default;
Quiz( const Question &question, const QuestionOptions &options, const TagContainer &tag_c )
: quiz_{ question }, quiz_options_( options ), quiz_tag_{ tag_c } {}
Quiz( std::ifstream &file_handle );
Question quiz_;
QuestionOptions quiz_options_;
TagContainer quiz_tag_;
private:
friend class boost::serialization::access;
template<typename Archive>
void serialize( Archive &archive, const unsigned int version )
{
archive & quiz_;
archive & quiz_options_;
archive & quiz_tag_;
}
};
std::ostream& operator<<( std::ostream &os, const Question &question );
std::ostream& operator<<( std::ostream &os, const QuestionOptions &options );
}
#endif
namespace QUIZMAKER.cpp
#include "Quiz_Maker.h"
#include "Exception.h"
#include <sstream>
#include <algorithm>
namespace QuizMaker
{
std::ostream& operator<<( std::ostream& os, const Question& question )
{
os << "Title: " << question.title_ << '\n';
os << question.question_;
return os;
}
std::ostream& operator<<( std::ostream& os, const QuestionOptions& opt )
{
int alphabelt = 65;
for( const auto options : opt.options_ )
os << char( alphabelt++ ) << ". " << options << '\n';
return os;
}
std::ostream& operator<<( std::ostream &os, const TagContainer &tag_c )
{
for( const auto tag : tag_c.tag_container_ )
os << "[" << tag << "] ";
return os;
}
}
EXCEPTION.H
#ifndef QUIZ_EXCEPTION_H_
#define QUIZ_EXCEPTION_H_
#include <fstream>
struct Exception
{
bool ofile_handle_error( std::ofstream &file_handle );
bool ifile_handle_error( std::ifstream &file_handle );
void throw_get_error() { throw std::invalid_argument( "Access error: Record not found." ); }
};
#endif
EXCEPTION.CPP
#include "Exception.h"
#include <iostream>
bool Exception::ofile_handle_error( std::ofstream &file_handle )
{
if( !file_handle ) {
std::cerr << "Write Error: File cannot be accessed.";
return true;
}
return false;
}
bool Exception::ifile_handle_error( std::ifstream &file_handle )
{
if( !file_handle ) {
std::cerr << "Read Error: File cannot be accessed.";
return true;
}
return false;
}
PERSISTENCE_MGR.H
#ifndef QUIZ_PERSISTENCEMGR_H_
#define QUIZ_PERSISTENCEMGR_H_
#include "Quiz_Application.h"
struct PersistenceMgr
{
void output( std::ofstream& file_handle, const QuizApplication &app );
QuizApplication input( std::ifstream& file_handle );
};
#endif
PERSISTENCE_MGR.CPP
#include "PersistenceMgr.h"
#include "Exception.h"
void PersistenceMgr::output( std::ofstream& file_handle, const QuizApplication& app )
{
Exception e;
if( e.ofile_handle_error( file_handle ) )
return;
boost::archive::text_oarchive archive{ file_handle };
archive << app;
}
QuizApplication PersistenceMgr::input( std::ifstream& file_handle )
{
Exception e;
QuizApplication app{};
if( e.ifile_handle_error( file_handle ) )
return app;
boost::archive::text_iarchive archive{ file_handle };
archive >> app;
return app;
}
QUIZ_APPLICATION.H
#ifndef QUIZ_QUIZAPPLICATION_H_
#define QUIZ_QUIZAPPLICATION_H_
#include "Quiz_Maker.h"
#include <vector>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/vector.hpp>
using namespace QuizMaker;
using QuizContainer = std::vector<QuizMaker::Quiz>;
class QuizApplication
{
public:
QuizApplication() = default;
QuizApplication( const QuizContainer &quiz_c )
: quiz_container_( quiz_c ) {}
QuizApplication( std::ifstream &file_handle );
void display();
QuizApplication& append( const QuizMaker::Quiz &quiz );
QuizApplication& remove( const std::string &title );
QuizContainer::const_iterator get( const std::string &title ) const;
QuizContainer::iterator get( const std::string &title );
unsigned count() const { return quiz_container_.size(); }
private:
QuizContainer quiz_container_;
std::vector<std::string> tokenize( const std::string &line );
TagContainer process_tag( const std::vector<std::string> &tokens );
Question process_question( const std::vector<std::string> &tokens );
void process_question_title( Question &question, const std::vector<std::string> &tokens );
QuestionOptions process_options( const std::vector<std::string> &tokens );
void process_correct_options( QuestionOptions &question_options, const std::vector<std::string> &tokens );
friend class boost::serialization::access;
template<typename Archive>
void serialize( Archive &archive, const unsigned int version )
{
archive & quiz_container_;
}
};
#endif
QUIZ_APPLICATION.CPP
#include "Quiz_Application.h"
#include "Exception.h"
#include <vector>
using namespace QuizMaker;
QuizApplication::QuizApplication( std::ifstream &file_handle )
{
Exception e;
if( !e.ifile_handle_error( file_handle ) ) {
std::string quiz_text;
std::string word;
while( !file_handle.eof() )
{
file_handle >> word;
if( word == "<quiz>" ) {
continue;
}
if( word == "</quiz>" )
{
QuizMaker::Quiz quiz_obj{};
std::vector<std::string> tokens = tokenize( quiz_text );
quiz_obj.quiz_tag_ = process_tag( tokens );
quiz_obj.quiz_ = process_question( tokens );
quiz_obj.quiz_options_ = process_options( tokens );
tokens.clear();
quiz_text = "";
quiz_container_.push_back( quiz_obj );
continue;
}
quiz_text += word += " ";
}
}
}
std::vector<std::string> QuizApplication::tokenize( const std::string &line )
{
std::vector<std::string> tokens;
std::stringstream stream( line );
std::string intermediate;
while( std::getline( stream, intermediate, ' ' ) )
tokens.push_back( intermediate );
return tokens;
}
TagContainer QuizApplication::process_tag( const std::vector<std::string> &tokens )
{
TagContainer tag;
auto iter = std::find( tokens.cbegin(), tokens.cend(), "<tag>" );
if( iter == tokens.cend() )
return tag;
auto iter2 = std::find( iter + 1, tokens.cend(), "</tag>" );
if( iter2 == tokens.cend() )
return tag;
for( auto it = iter + 1; it != iter2; ++it )
tag.tag_container_.push_back( *it );
return tag;
}
Question QuizApplication::process_question( const std::vector<std::string> &tokens )
{
Question question_obj;
auto iter = std::find( tokens.cbegin(), tokens.cend(), "<question>" );
if( iter == tokens.cend() )
return question_obj;
auto iter2 = std::find( iter + 1, tokens.cend(), "</question>" );
if( iter2 == tokens.cend() )
return question_obj;
for( auto it = iter + 1; it != iter2; ++it )
{
question_obj.question_ += *it;
question_obj.question_ += " ";
}
process_question_title( question_obj, tokens );
return question_obj;
}
void QuizApplication::process_question_title( Question &question_obj, const std::vector<std::string> &tokens )
{
auto iter = std::find( tokens.cbegin(), tokens.cend(), "<title>" );
if( iter == tokens.cend() )
return;
auto iter2 = std::find( iter + 1, tokens.cend(), "</title>" );
if( iter2 == tokens.cend() )
return;
for( auto it = iter + 1; it != iter2; ++it )
{
question_obj.title_ += *it;
if( it != iter2 - 1 )
question_obj.title_ += " ";
}
}
QuestionOptions QuizApplication::process_options( const std::vector<std::string> &tokens )
{
QuestionOptions question_options;
auto iter = std::find( tokens.cbegin(), tokens.cend(), "<option>" );
if( iter == tokens.cend() )
return question_options;
auto iter2 = std::find( tokens.cbegin(), tokens.cend(), "</option>" );
if( iter2 == tokens.cend() )
return question_options;
auto iter_sub1 = std::find( iter + 1, iter2, "<option-item>" );
while( iter_sub1 != iter2 )
{
auto iter_sub2 = std::find( iter_sub1, iter2, "</option-item>" );
if( iter_sub2 == iter2 )
return question_options;
std::string option;
for( auto it = iter_sub1 + 1; it != iter_sub2; ++it )
{
option += *it;
option += " ";
}
question_options.options_.push_back( option );
iter_sub1 = std::find( iter_sub2, iter2, "<option-item>" );
}
process_correct_options( question_options, tokens );
return question_options;
}
void QuizApplication::process_correct_options( QuestionOptions &question_options, const std::vector<std::string> &tokens )
{
auto iter = std::find( tokens.cbegin(), tokens.cend(), "<correct-option>" );
if( iter == tokens.cend() )
return;
auto iter2 = std::find( tokens.cbegin(), tokens.cend(), "</correct-option>" );
if( iter2 == tokens.cend() )
return;
question_options.correct_option_ = *(iter + 1);
}
void QuizApplication::display()
{
for( const auto item : quiz_container_ )
std::cout << item.quiz_tag_ << "\n" << item.quiz_ << "\n" << item.quiz_options_ << "\n";
}
QuizApplication& QuizApplication::append( const QuizMaker::Quiz &quiz )
{
quiz_container_.push_back( quiz );
return *this;
}
QuizApplication& QuizApplication::remove( const std::string &title )
{
auto iter = get( title );
quiz_container_.erase( iter );
return *this;
}
QuizContainer::const_iterator QuizApplication::get( const std::string &title ) const
{
for( auto beg = quiz_container_.cbegin(); beg != quiz_container_.cend(); ++beg )
{
if( beg->quiz_.title_ == title )
return beg;
}
Exception e;
e.throw_get_error();
}
QuizContainer::iterator QuizApplication::get( const std::string &title )
{
for( auto beg = quiz_container_.begin(); beg != quiz_container_.end(); ++beg )
{
if( beg->quiz_.title_ == title )
return beg;
}
Exception e;
e.throw_get_error();
}
main.cpp
#include "Quiz_Application.h"
#include "PersistenceMgr.h"
#include "Quiz_Maker.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
using namespace QuizMaker;
int main()
{
std::ifstream file_handle{ "file.txt" };
QuizApplication quiz_app{ file_handle };
Question new_question{ "STL Containers", "The _______ container member returns true if no element is present otherwise returns false" };
QuestionOptions my_options;
my_options.options_ = {
"emplace",
"fill",
"empty",
"size"
};
my_options.correct_option_ = { "C" };
TagContainer my_tags;
std::vector<std::string> tags = { "STL", "Programming", "Algorithm" };
my_tags.tag_container_ = tags;
Quiz new_quiz{ new_question, my_options, my_tags };
quiz_app.append( new_quiz );
/* delete a quiz */
quiz_app.remove( "STL Containers" );
/* modify a quiz */
auto iter = quiz_app.get( "STL Equality Comparison" );
iter->quiz_tag_.tag_container_.push_back( "STL" );
quiz_app.display();
/* Save to file */
PersistenceMgr per_mgr;
std::ofstream file_handle2{ "app.txt" };
per_mgr.output( file_handle2, quiz_app );
std::cout << "Quit total count: " << quiz_app.count() << "\n";
/* Load Quiz App */
std::ifstream file_handle3{ "app.txt" };
QuizApplication quiz_loaded = per_mgr.input( file_handle3 );
std::cout << "Successfully loaded the application\n\n";
quiz_loaded.display();
}
This is a sample question text file
<quiz>
<tag> C++ Programming </tag>
<title> STL Sorting Iterator </title>
<question> The sort algorithm requires a(n) _____________ iterator </question>
<option>
<option-item> random-access </option-item>
<option-item> bidirectional </option-item>
<option-item> forward </option-item>
<option-item> None of the above </option-item>
</option>
<correct-option> A </correct-option>
</quiz>
<quiz>
<tag> C++ Programming </tag>
<title> STL Equality Comparison </title>
<question> The ___________ algorithm compares two sequence for equality </question>
<option>
<option-item> find </option-item>
<option-item> equal </option-item>
<option-item> fill_n </option-item>
<option-item> copy </option-item>
</option>
<correct-option> B </correct-option>
</quiz>
<quiz>
<tag> C++ Programming </tag>
<title> STL Remove_if Algorithm </title>
<question> The remove_if algorithm does not modify the number of elements in the container,
but it does move to the beginning of the containe all element that are not remove
</question>
<option>
<option-item> True </option-item>
<option-item> False </option-item>
<option-item> Not exactly </option-item>
<option-item> Maybe </option-item>
</option>
<correct-option> A </correct-option>
</quiz>
Now that you are really getting into OOP it might be a good idea to try creating your own dynamically linked libraries to implement some portions the your solutions.
Please, can you explain further? \$\endgroup\$