Beginner asks for suggestions

Hello!

I cant figure out the best way how my database should look like, so my problem is the following:

I want to create a windows gui program, where i can get informations about Products. First i want to write my classes i'm going to use in this windows form, following the following logic:

In my code everything is a Product thus there is a Product base class, for example a car is a product, but also it's wheel is a product in a factory that produces wheels, and both of them have a productID.

A car contains a wheel, so i want to pick a specific type of wheel and put it in the car. The car refers to the wheel as a Product, because this way i can store everything in the same array or vector, the wheel, the tire, the seat, etc. My program is going to get the products from a data type class, and here comes my problem:

How to store these kind of datas? I had 3 ideas, maybe they all are wrong (in this case please suggest a better solution)

1. Store all the Product pointers in a std::vector<Product* > vProduct;
cons:
I couldn't figure out a resource saving solution to get datas from this vector. For example, if i want to check all the wheels, i have to
-Go through the entire vector (may contain a lot of elements)
-1.1 Dynamic cast everything to get what type of object it is, or
-1.2 Write a virtual identifier getter method, where i can check if the element is a wheel, tire, etc.., but this doesnt sound quite elegant, because it's like if you put a label with the text "car" on a car.

2. Save the datas into separate vectors or arrays, like: std::vector<Wheel* >...
pros:
-Easy to use in drop down lists
-Uses less resources if you know what you are searching for.
cons:
-Makes the code a bit more complicated.
If there are only a few different product types and addition of new product types in the future is is exprected to be unlikely/rare, option 2. may be a good idea.

Otherwise, a multimap where the key is the type (std::type_index) of the product and the mapped value is the (smart) pointer to the product would allow fast, convenient look up for all products of a specified type.

Something along these lines:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <memory>
#include <unordered_map>
#include <typeindex>
#include <type_traits>
#include <string>

struct product ;
// http://en.cppreference.com/w/cpp/types/type_index
// http://en.cppreference.com/w/cpp/memory/unique_ptr
using product_map = std::unordered_multimap< std::type_index, std::unique_ptr<product> > ;

template < typename PRODUCT_TYPE, typename... CONSTRUCTOR_ARGS >
auto make_product( product_map& pmap, CONSTRUCTOR_ARGS&&... args ) // amortised O(1)
{
    // http://en.cppreference.com/w/cpp/types/is_base_of
    static_assert( std::is_base_of<product,PRODUCT_TYPE>::value, "must be a derived class of product" ) ;

    // http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique
    return pmap.emplace( typeid(PRODUCT_TYPE), // key is the type_index of the product type
                         std::make_unique<PRODUCT_TYPE>( std::forward<CONSTRUCTOR_ARGS>(args)... ) ) ;
}

// syntactic sugar to support range-based loops to access a range of products in the map
struct product_range
{
    std::pair< product_map::iterator, product_map::iterator > range ;
    auto begin() { return range.first ; }
    auto end() { return range.second ; }
};

// retrieve (iterators to) all products of a specified type from the map
// average O(m) where m is the number of products of type PRODUCT_TYPE
// http://en.cppreference.com/w/cpp/container/unordered_multimap/equal_range
template < typename PRODUCT_TYPE > product_range all_of_type( product_map& pmap )
{ return { pmap.equal_range( typeid(PRODUCT_TYPE) ) } ; }

struct product
{
    product( std::string id ) : id( std::move(id) ) {}
    virtual ~product() = default ;
    // ...
    std::string id ;
};

struct car : product { using product::product ; /* ... */ };

struct wheel : product
{
    wheel( std::string id, int radius ) : product( std::move(id) ), radius(radius) {}
    int radius ;
    // ...
};

int main()
{
    product_map pmap ;

    make_product<car>( pmap, "car 0001" ) ;
    make_product<wheel>( pmap, "wheel 1001", 23 ) ;
    make_product<car>( pmap, "car 0023" ) ;
    make_product<wheel>( pmap, "wheel 1056", 18 ) ;
    make_product<car>( pmap, "car 0172" ) ;
    make_product<wheel>( pmap, "wheel 1048", 20 ) ;
    make_product<wheel>( pmap, "wheel 1162", 16 ) ;

    std::cout << "cars:\n-----\n" ; // all the cars
    for( const auto& prod : all_of_type<car>(pmap) ) std::cout << prod.second->id << '\n' ;

    std::cout << "\nwheels:\n-----\n" ; // all the cars
    for( const auto& prod : all_of_type<wheel>(pmap) ) std::cout << prod.second->id << '\n' ;
}

http://coliru.stacked-crooked.com/a/1f04eac6e491ffa9
This is an amazing solution from which I've learnt a lot, thank you. I was just wondering if the first and second iterators returned by std::unordered_multimap::equal_range() could have been used instead, thereby removing the need for struct product_range and template function all_of_type?
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <memory>
#include <unordered_map>
#include <typeindex>
#include <type_traits>
#include <string>
#include <algorithm>

struct product ;
// http://en.cppreference.com/w/cpp/types/type_index
// http://en.cppreference.com/w/cpp/memory/unique_ptr
using product_map = std::unordered_multimap< std::type_index, std::unique_ptr<product> > ;

template < typename PRODUCT_TYPE, typename... CONSTRUCTOR_ARGS >
auto make_product( product_map& pmap, CONSTRUCTOR_ARGS&&... args ) // amortised O(1)
{
    // http://en.cppreference.com/w/cpp/types/is_base_of
    static_assert( std::is_base_of<product,PRODUCT_TYPE>::value, "must be a derived class of product" ) ;

    // http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique
    return pmap.emplace( typeid(PRODUCT_TYPE), // key is the type_index of the product type
                         std::make_unique<PRODUCT_TYPE>( std::forward<CONSTRUCTOR_ARGS>(args)... ) ) ;
}

// syntactic sugar to support range-based loops to access a range of products in the map
/*struct product_range
{
    std::pair< product_map::iterator, product_map::iterator > range ;
    auto begin() { return range.first ; }
    auto end() { return range.second ; }
};

// retrieve (iterators to) all products of a specified type from the map
// average O(m) where m is the number of products of type PRODUCT_TYPE
// http://en.cppreference.com/w/cpp/container/unordered_multimap/equal_range
template < typename PRODUCT_TYPE > product_range all_of_type( product_map& pmap )
{ return { pmap.equal_range( typeid(PRODUCT_TYPE) ) } ; }
*/

struct product
{
    product( std::string id ) : id( std::move(id) ) {}
    virtual ~product() = default ;
    // ...
    std::string id ;
};

struct car : product { using product::product ; /* ... */ };

struct wheel : product
{
    wheel( std::string id, int radius ) : product( std::move(id) ), radius(radius) {}
    int radius ;
    // ...
};

int main()
{
    product_map pmap ;

    make_product<car>( pmap, "car 0001" ) ;
    make_product<wheel>( pmap, "wheel 1001", 23 ) ;
    make_product<car>( pmap, "car 0023" ) ;
    make_product<wheel>( pmap, "wheel 1056", 18 ) ;
    make_product<car>( pmap, "car 0172" ) ;
    make_product<wheel>( pmap, "wheel 1048", 20 ) ;
    make_product<wheel>( pmap, "wheel 1162", 16 ) ;

    std::cout << "cars:\n-----\n" ; // all the cars
    auto car_itr = pmap.equal_range(typeid(car));
    for (auto itr = car_itr.first; itr != car_itr.second; ++itr)
    {
        std::cout << itr->second->id << '\n' ;
    }

    std::cout << "\nwheels:\n-----\n" ; // all the wheels
    auto wheel_itr = pmap.equal_range(typeid(wheel));
    for( auto itr = wheel_itr.first; itr != wheel_itr.second; ++itr )
        std::cout << itr->second->id << '\n' ;
}
> if first and second iterators returned by std::unordered_multimap::equal_range() could have been used instead

Yes. As indicated in the comments, product_range is just syntactic sugar.
Topic archived. No new replies allowed.