1
\$\begingroup\$

I did the following two excercise from PPP by Stroustrup:

Define an Order class with (customer) name, address, data, and vector<Purchase> members. Purchase is a class with a (product) name, unit_price, and count members. Define a mechanism for reading and writing Orders to and fronm a file. Define a mechanism for printing Orders Create a file of at least ten Orders, read it into a vector<Order>, sort it by name ( of customer), and write it back out to a file. Create annother file of at least ten Orders of which about a third are the same as in the first file, read it into a list<Order>, sort it by address (of customer), and write it back out to a file. Merge the two files into a third using std::merge().

Compute the total value of the orders in the two files from the previous excercise. The value of an individual Purchase is (of course) its unit_price*count.

During the test of the program i found one error which surprised me that it compiled.

In Order.h i created a template function

template<typename Orders> void write_to_file(const std::string& filename, const Orders& orders)

If i run this function in the Order_test.cpp without template argument like this:

write_to_file(first_sorted_by_customer_name_filename, first_orders);

it still compiles. Is the argument optional because its deduced by the passed argument? I changed it back to

write_to_file<std::vector<Order>>(first_sorted_by_customer_name_filename, first_orders);

But i wonder if this is intended. I use MSVS2017.

Also i tryed at first to use std::merge with a vector and a list as a argument but it didnt compile. So ihad to change the list to a vector aswell. I wonder why.

Please feel free to comment on any code smells you can find in the solution.

What could have been done better/ more efficient? Any bad practices?

Heres the full code:

Purchase.h

#ifndef PURCHASE_17082018_GUARD
#define PURCHASE_17082018_GUARD

#include <iosfwd>
#include <string>

namespace order {

    class Purchase {
    public:
        Purchase() = default;

        Purchase(const std::string& product_name, double price, int count)
            :m_product_name{ product_name }, m_unit_price{ price }, m_count{ count }
        {
        }


        std::string product_name()          const { return m_product_name; }
        double      unit_price()            const { return m_unit_price; }
        int         count()                 const { return m_count; }
    private:
        std::string m_product_name{};   
        double      m_unit_price{};
        int         m_count{};
    };

    std::istream& operator>>(std::istream& is, Purchase& obj);
    std::ostream& operator<<(std::ostream& os, const Purchase& obj);

}

#endif

Purchase.cpp

#include "Purchase.h"

#include <iostream>
#include <sstream>

namespace order {

    std::istream& operator>>(std::istream& is, Purchase& obj)
    {
        std::string name;
        std::getline(is, name, ';');
        if (!is || name.empty()) {
            is.setstate(std::ios::failbit);
            return is;
        }

        std::string price_string;
        std::getline(is, price_string, ';');
        if (!is || price_string.empty()) {
            is.setstate(std::ios::failbit);
            return is;
        }

        double price{};
        try {
            price = std::stod(price_string);
        }
        catch (...) {   // catch convert fault
            is.setstate(std::ios::failbit);
            return is;
        }

        std::string count_string;
        std::getline(is, count_string, ';');
        if (!is || count_string.empty()) {
            is.setstate(std::ios::failbit);
            return is;
        }

        int count{};
        try {
            count = std::stoi(count_string);
        }
        catch (...) {  // catch convert fault
            is.setstate(std::ios::failbit);
            return is;
        }

        Purchase res{ name,price,count };

        obj = res;

        return is;
    }

    std::ostream& operator<<(std::ostream& os, const Purchase& obj)
    {
        os << obj.product_name() << ';' << obj.unit_price() << ';' << obj.count() << ';';
        return os;
    }
}

Order.h

#ifndef ORDER_17082018_GUARD
#define ORDER_17082018_GUARD

#include "Purchase.h"

#include <fstream>
#include <iosfwd>
#include <string>
#include <vector>

namespace order {

    class Order {
    public:
        Order() = default;

        Order(const std::string& customer_name, const std::string& address, const std::string& data)
            : m_customer_name{ customer_name }, m_address{ address }, m_data{ data }
        {
        }

        Order(const std::string& customer_name, const std::string& address, const std::string& data, const std::vector<Purchase>& purchases)
            : m_customer_name{ customer_name }, m_address{ address }, m_data{ data }, m_purchases{ purchases }
        {
        }

        void add_purchase(const Purchase& p)    { m_purchases.push_back(p); }
        std::string customer_name()             const { return m_customer_name;}
        std::string address()                   const { return m_address; }
        std::string data()                      const { return m_data; }
        std::vector<Purchase> get_purchases()   const { return m_purchases; }
    private:
        std::string m_customer_name{};      //customer name
        std::string m_address{};
        std::string m_data{};
        std::vector<Purchase> m_purchases{};
    };

    std::istream& operator>>(std::istream& is, Order& obj);
    std::ostream& operator<<(std::ostream& os, const Order& obj);

    template<typename Orders> void write_to_file(const std::string& filename, const Orders& orders)
    {
        std::ofstream ofs{ filename };
        if (!ofs) {
            throw std::runtime_error(
                "template<typename Orders> void write_to_file(const std::string& filename, const Orders& orders)\n"
                "file " + filename + " could not be opened\n"
            );
        }
        for (const auto& x : orders) {
            ofs << x << '\n';
        }
    }

    template<typename Orders> Orders read_from_file(const std::string& filename)
    {
        std::ifstream ifs{ filename };
        if (!ifs) {
            throw std::runtime_error(
                "template<typename Orders> Orders read_from_file(const std::string& filename)\n"
                "file " + filename + " could not be opened\n"
            );
        }
        Orders orders;

        for (Order order; ifs >> order;) {
            orders.push_back(order);
        }
        return orders;
    }

    template<typename Orders>
    double compute_total_value(const Orders& orders) 
    {
        double order_sum{};
        for (const auto& order : orders) {

            double purchase_sum{};
            for (const auto& purchase : order.get_purchases()) {
                purchase_sum += purchase.count() * purchase.unit_price();
            }
            order_sum += purchase_sum;
        }
        return order_sum;
    }
}

#endif

Order.cpp

#include "Order.h"

#include <iostream>
#include <sstream>

namespace order {

    std::istream& operator>>(std::istream& is, Order& obj)
    {
        std::string line;
        std::getline(is, line);

        std::istringstream ist{ line };

        std::string name;
        std::getline(ist, name, ';');
        if (!ist || name.empty()) {
            is.setstate(std::ios::failbit);
            return is;
        }

        std::string address;
        std::getline(ist, address, ';');
        if (!ist || address.empty()) {
            is.setstate(std::ios::failbit);
            return is;
        }

        std::string data;
        std::getline(ist, data, ';');
        if (!ist || data.empty()) {
            is.setstate(std::ios::failbit);
            return is;
        }

        std::vector<Purchase> purchases;
        for (Purchase p; ist >> p; ) {
            purchases.push_back(p);
        }

        Order res{ name,address,data,purchases };

        obj = res;

        return is;
    }

    std::ostream& operator<<(std::ostream& os, const Order& obj)
    {
        os << obj.customer_name() << ';' << obj.address() << ';' << obj.data()<<';';

        for (const auto &x : obj.get_purchases()) {
            os << x;
        }
        return os;
    }
}

Order_test.h

#ifndef ORDER_TEST_GUARD_17082018
#define ORDER_TEST_GUARD_17082018

#include "Order.h"

namespace order {

    void fill_first_file();
    void fill_second_file();

    void test();
}

#endif

Order_test.cpp

#include "Order_test.h"

#include <algorithm>
#include <vector>
#include <list>
#include <iostream>

namespace order {

    constexpr auto first_filename = "first.txt";
    constexpr auto first_sorted_by_customer_name_filename = "first_sorted_by_customer.txt";

    constexpr auto second_filename = "second.txt";
    constexpr auto second_sorted_by_address_filename = "second_sorted_by_address.txt";

    constexpr auto merged_filename = "merged.txt";

    void fill_first_file()
    {
        std::vector<order::Order> orders{
            order::Order{
            "f_customer","f_address","f_data",
            std::vector<order::Purchase>{order::Purchase{ "fa_product",90.0,13 },order::Purchase{ "fb_product",7.1,8 },order::Purchase{ "fc_product",3.0,3 }}
        },
            order::Order{
            "d_customer","d_address","d_data",
            std::vector<order::Purchase>{order::Purchase{ "da_product",40.0,11 },order::Purchase{ "db_product",12.1,5 },order::Purchase{ "dc_product",3.0,3 }}
        },
            order::Order{
            "h_customer","h_address","h_data",
            std::vector<order::Purchase>{order::Purchase{ "ha_product",560.0,34 },order::Purchase{ "hb_product",12.1,5 },order::Purchase{ "hc_product",11.0,3 }}
        },
            order::Order{
            "a_customer","a_address","a_data",
            std::vector<order::Purchase>{order::Purchase{ "aa_product",100.0,1 },order::Purchase{ "ab_product",12.1,5 },order::Purchase{ "ac_product",11.0,3 }}
        },
            order::Order{
            "i_customer","i_address","i_data",
            std::vector<order::Purchase>{order::Purchase{ "ia_product",70.0,4 },order::Purchase{ "ic_product",11.0,3 }}
        },
            order::Order{
            "b_customer","b_address","b_data",
            std::vector<order::Purchase>{order::Purchase{ "ba_product",50.0,5 }}
        },
            order::Order{
            "j_customer","j_address","j_data",
            std::vector<order::Purchase>{order::Purchase{ "ja_product",20.0,5 },order::Purchase{ "jb_product",12.1,5 }}
        },
            order::Order{
            "c_customer","c_address","c_data",
            std::vector<order::Purchase>{order::Purchase{ "ca_product",30.0,10 },order::Purchase{ "cb_product",14.1,5 }}
        },
            order::Order{
            "g_customer","g_address","g_data",
            std::vector<order::Purchase>{order::Purchase{ "ga_product",10.0,18 }}
        },
            order::Order{
            "e_customer","e_address","e_data",
            std::vector<order::Purchase>{order::Purchase{ "ea_product",180.0,123 }}
        }
        };

        write_to_file<std::vector<Order>>(first_filename, orders);
    }

    void fill_second_file()
    {

        std::vector<order::Order> orders{

            order::Order{
            "f_customer","f_address","f_data",
            std::vector<order::Purchase>{order::Purchase{ "fa_product",90.0,13 },order::Purchase{ "fb_product",7.1,8 },order::Purchase{ "fc_product",3.0,3 }}
        },
            order::Order{
            "d_customer","d_address","d_data",
            std::vector<order::Purchase>{order::Purchase{ "da_product",40.0,11 },order::Purchase{ "db_product",12.1,5 },order::Purchase{ "dc_product",3.0,3 }}
        },
            order::Order{
            "h_customer","h_address","h_data",
            std::vector<order::Purchase>{order::Purchase{ "ha_product",560.0,34 },order::Purchase{ "hb_product",12.1,5 },order::Purchase{ "hc_product",11.0,3 }}
        },
            order::Order{
            "a_customer","a_address","a_data",
            std::vector<order::Purchase>{order::Purchase{ "aa_product",100.0,1 },order::Purchase{ "ab_product",12.1,5 },order::Purchase{ "ac_product",11.0,3 }}
        },
            // data different from first file
            order::Order{
            "2i_customer","2i_address","2i_data",
            std::vector<order::Purchase>{order::Purchase{ "2ia_product",270.0,24 },order::Purchase{ "2ic_product",211.0,23 }}
        },
            order::Order{
            "2b_customer","2b_address","2b_data",
            std::vector<order::Purchase>{order::Purchase{ "2ba_product",250.0,25 }}
        },
            order::Order{
            "2j_customer","2j_address","2j_data",
            std::vector<order::Purchase>{order::Purchase{ "2ja_product",220.0,52 },order::Purchase{ "2jb_product",212.1,25 }}
        },
            order::Order{
            "2c_customer","2c_address","2c_data",
            std::vector<order::Purchase>{order::Purchase{ "2ca_product",230.0,210 },order::Purchase{ "2cb_product",214.1,25 }}
        },
            order::Order{
            "2g_customer","2g_address","2g_data",
            std::vector<order::Purchase>{order::Purchase{ "2ga_product",210.0,218 }}
        },
            order::Order{
            "2e_customer","2e_address","2e_data",
            std::vector<order::Purchase>{order::Purchase{ "2ea_product",2180.0,2123 }}
        }
        };

        write_to_file<std::vector<Order>>(second_filename, orders);
    }

    void test()
    {
        fill_first_file();
        fill_second_file();

        std::vector<Order> first_orders = read_from_file<std::vector<Order>>(first_filename);
        std::sort(
            first_orders.begin(), first_orders.end(), 
            [](const Order& a, const Order& b) { return (a.customer_name() < b.customer_name()); });
        write_to_file<std::vector<Order>>(first_sorted_by_customer_name_filename, first_orders);

        std::list<Order> second_orders = read_from_file<std::list<Order>>(second_filename);
        second_orders.sort([](const Order& a, const Order& b) { return (a.address() < b.address()); });
        write_to_file<std::list<Order>>(second_sorted_by_address_filename, second_orders);

        std::vector<Order> second_orders_no_list = read_from_file<std::vector<Order>>(second_filename);
        std::sort(
            second_orders_no_list.begin(),second_orders_no_list.end(),
            [](const Order& a, const Order& b) { return (a.customer_name() < b.customer_name()); });

        std::vector<Order> merged_orders;
        std::merge(
            first_orders.begin(), first_orders.end(), 
            second_orders_no_list.begin(), second_orders_no_list.end(), 
            std::back_inserter(merged_orders), 
            [](const Order& a, const Order& b) { return (a.customer_name() < b.customer_name()); });
        write_to_file<std::vector<Order>>(merged_filename, merged_orders);

        std::cout.precision(2);
        std::cout << "total value from " << first_filename << ":" << std::fixed<<compute_total_value<std::vector<Order>>(first_orders) << '\n';
        std::cout << "total value from " << second_filename << ":" << std::fixed<<compute_total_value<std::list<Order>>(second_orders) << '\n';
    }
}

main.cpp

#include <iostream>
#include "Order_test.h"

int main()
try {
    order::test();
    std::cin.get();
}
catch (std::runtime_error& e) {
    std::cerr << e.what() << "\n";
    std::cin.get();
}
catch (...) {
    std::cerr << "unknown error " << "\n";
    std::cin.get();
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.