6
\$\begingroup\$

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

  1. 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.
  2. 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?
  3. Some of my method functions were supposed to return a value for example get(), but in a scenario where an object of type Quiz 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>
\$\endgroup\$
4
  • 2
    \$\begingroup\$ Not having a default constructor forces the class to be constructed as expected. There is no problem with the class not having a default constructor. \$\endgroup\$
    – pacmaninbw
    Nov 15, 2020 at 13:59
  • 2
    \$\begingroup\$ Since the boost libraries are primarily implemented as header files, I expect that your compile times were longer as well. 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. \$\endgroup\$
    – pacmaninbw
    Nov 15, 2020 at 14:03
  • 1
    \$\begingroup\$ 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\$ Nov 15, 2020 at 15:11
  • 1
    \$\begingroup\$ As an aside, consider "What's the difference between “STL” and “C++ Standard Library”?". \$\endgroup\$ Nov 15, 2020 at 18:55

4 Answers 4

11
\$\begingroup\$

Here are some things that may help you improve your code.

Use the library for XML data

It's peculiar to me that the code does manual parsing of XML tags, even though the Boost serialization library already has explicit support for XML. Since you specifically mention the size of the resulting application as a concern, one simple way to reduce the size would be to not duplicate things that are already in the library.

If it has private members, it should be a class

The Quiz struct really deserves to be a class, I think. Otherwise we could alter questions and answers and options at will, which seems a bit suspect to me. Also, see C.8

Rethink your class structure

What you name Quiz ought to be called Question. We have a dizzying array of similarly named classes and namespaces including QuizApplication, QuizContainer, QuizMaker, Quiz, Question, QuestionOptions which seem to be largely trivial wrappers for each other. I would strongly suggest that a Quiz is a container for Question objects and that is probably all that is necessary. Also, a Question should contain the question, the answer(s) and subject tags. Instead of requiring a separate value to indicate which is the correct option, I'd suggest simply making the first answer (as stored and serialized) the correct answer and to simply randomize the display of options when presenting them for a quiz-taker. Also, does every question really need a title?

Unused parameters should be unnamed

The specializations of boost::serialization::access do not use the version parameter which causes a number of compiler warnings. To signal that the parameter is deliberately unused, omit the name. See F.9

Minimize the interface

The main.cpp file includes Quiz_Application.h, PersistenceMgr.h and Quiz_Maker.h. In addition to the inconsistencies in the use of abbreviations and underscores, this suggests to me that the interface is not yet optimal. The PersistenceMrg struct seems particularly worth of elimination, since it provides only functions that probably ought to be functions of the QuizApplication class instead. Rather than convenience functions, these appear to be inconvenience functions which obfuscate rather than clarify. Also, why do we need both a QuizApplication and a QuizMaker? The Exception class also serves little value except to confuse, since it does not appear to have any relationship to std::exception.

Use standard library functions

Both versions of QuizApplication::get() which you ask about (and the compiler complains about) could easily be replace with std::find(). Note that std::find() returns the ending iterator if the value is not found, rather than throwing an exception. I would advise that behavior.

Use better naming

The QuizApplication class has member functions display, append and remove which are not great names, but at least somewhat explanatory. However, process_correct_options and process_options are quite vague names that could be improved, or as per my first suggestion, complelely eliminated. I'd also suggest that the entire class is probably not needed if you refactor your classes as suggested above.

Create better constructors

If I were using this library, I'd want to be able to create a question like this:

Question q{"Value of Pi",     // title
            "The value of pi is approximately 3.14159.",   // question
            {"True", "False"},  // choices, first one is the correct one
            {"Mathematics", "Constants"}  // tags
        };

I would also want to be able to create a Quiz of such questions using a std::initializer_list

Consider alternatives

It depends on your needs and preferences, but it's probably worth considering potential alternatives to the Boost serialization library, such as perhaps Boost.PropertyTree or perhaps Google's Protocol Buffers.

Fix the minor bug

The code prints a spurious Title: at the end of every quiz when display is called. That's because there is a fault in the code which causes it to create four questions when reading only three; the fourth is empty and invalid.

\$\endgroup\$
7
  • \$\begingroup\$ Unused parameters can also be declared [[maybe_unused]]. \$\endgroup\$
    – Casey
    Nov 15, 2020 at 17:12
  • \$\begingroup\$ The OP's implementation of options and corrrect-option allows for answers other than the first. How would you go about making sure the user can't always pick the first choice and get the question correct? \$\endgroup\$
    – Casey
    Nov 15, 2020 at 17:14
  • 1
    \$\begingroup\$ @Casey: As I wrote in my answer, how they are stored isn't necessarily how they would presented to someone taking the quiz. I'd randomize the display of the answers. \$\endgroup\$
    – Edward
    Nov 15, 2020 at 17:32
  • 1
    \$\begingroup\$ Also, [[maybe_unused]] shouldn't be used if the parameter is definitely unused. See stackoverflow.com/questions/49320810/… \$\endgroup\$
    – Edward
    Nov 15, 2020 at 17:39
  • \$\begingroup\$ All good points. :) \$\endgroup\$
    – Casey
    Nov 15, 2020 at 17:49
6
\$\begingroup\$

Don't re-invent the wheel. You are manually parsing an XML file. This has been done before and there exist many mature libraries that already do this. I personally use TinyXml2. It only requires a single header and source file instead of all of boost and is very fast.


It makes no sense for a Question to not have any text or choices associated with it, nor for a multiple-choice Quiz to have no questions. Creating a default question or quiz puts the classes in an invalid state, therefore, I would suggest removing the default constructors.


Instead of throwing, you can use the STL implementation of returning the end iterator when an object cannot be found. The user then would just have to check for it before using the result.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ The project is meant for practice purposes. \$\endgroup\$ Nov 15, 2020 at 0:19
  • 7
    \$\begingroup\$ @theProgrammer If you didn't want people to address the concerns you had on a code review site, why did you post it in the first place? \$\endgroup\$
    – Casey
    Nov 15, 2020 at 4:20
2
\$\begingroup\$

Overall Observations

Each question is showing real improvement in the code and in the quality of the question, nice job!

@Edward and others gave some very good reviews and covered everything I was going to cover.

Exceptions

As a concept and as a type / class exceptions already exist in C++. It might be better if the exceptions class was renamed to QuizExceptions and inherits either the base exception class or inherits from one of the exception sub classes such std::runtime_error or std::logic_error. This provides you with some basic exception methods and functions so that you don't have to roll your own.

Make your throws explicit rather than calling a function in the exception to execute the throw. That would remove the warning messages from the 2 versions of get().

Dynamically Linked Libraries

Since the 1990s there has been the concept of libraries that are loaded as necessary and aren't part of the main executable program. On Windows these libraries are known as DLLs (LIBNAME.dll) and on Linux and Unix these libraries are known as Shared Objects (LIBNAME.so). You have your choice of linking these libraries at link time or loading them and unloading them as necessary during the execution of the program. If you are running in an environment with limited memory such as in embedded programming and want to limit the size of the executable you can load the libraries at run time, otherwise I suggest that you link the libraries at build time using the linker.

On windows a dll can be loaded using LoadLibrary on Linux a shared library can be loaded using dlopen. Creating portable code to compile and run on both systems isn't possible without using ifdef to figure out which system you are on because the operating system gets involved with this operation.

Because this is implementing shared objects on both Windows and Linux these libraries can be implemented as classes. You will also want to read up on the Facade Design Pattern before you attempt this.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ If the called library is installed properly on a Linux system, there is no need to call dlopen explicitly. Just link when creating the program that needs the library and the loader will automagically take care of the rest. See this for an example. \$\endgroup\$
    – Edward
    Nov 15, 2020 at 23:06
  • \$\begingroup\$ @pacmaninbw Does this mean I wouldn't have to #include any header file? \$\endgroup\$ Nov 16, 2020 at 8:17
  • \$\begingroup\$ @theProgrammer Your code would have to be restructured but there would be still be some need for header files. Every dynamic library will have header files to define their interfaces. \$\endgroup\$
    – pacmaninbw
    Nov 16, 2020 at 13:57
1
\$\begingroup\$

Most of what I wanted to add has been covered by others, however

Bulk insert and assign

std::vector has bulk insert and assign functions insert and assign. Assign completely overwrites the contents, insert... inserts. When you use these, instead of pushing back space, then constructing, and repeating; the total area is reserved, and then all the items are copied at once.

Additionally, you should refactor so functions do the minimal amount of work necessary.

An example:

std::vector<std::string> QuizApplication::process_tag( const std::vector<std::string> &tokens )
{
    std::vector<std::string> tag;

    auto begin = std::find( tokens.cbegin(), tokens.cend(), "<tag>" );
    if( begin == tokens.cend() )
        return tag;

    auto end = std::find( std::next(begin), tokens.cend(), "</tag>" );
    if( end == tokens.cend() )
        return tag;

    tag.assign( std::next(begin) , end );

    return tag;
} 

You could then assign the result to a tag_container instead of needing to instantiate a complete tag container, and needing to jump through a lot of noisy indirection.

find_if & standard algorithms

In Quiz_application::get you're doing exactly what std::find_if does, with an extra exception if you don't find the target.

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();
}

// Is equivalent to
QuizContainer::iterator QuizApplication::get( const std::string &title )
{
    auto const title_match =
        [title](auto const & item){
            return item.title == title;
        };
    auto itr = std::find_if(cbegin(quiz_container_), cend(quiz_container_),title_match);
    if (itr != cend(quiz_container_))
        return itr;
    Exception e;
    e.throw_get_error();
}

The standard algorithms are your friends! Don't be afraid to lean on them!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.