Best way to retrieve multiple variables from nested objects for other object?

closed account (Ezyq4iN6)
I want to reach into an object, which is nested in other objects, and retrieve data to easily pass to another object. Please see the code below for a brief outline of the issue:

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
  int main()
  {
    Screen_Drawing screen;
    screen.draw_houses(some_arguments);
  }

  class Screen_Drawing
  {
    void draw_houses(some_arguments)
    {
      draw_rectangle(x, y, w, h);
    }
  }

  class Map
  {
    string name;
    vector<Island> islands_on_map
  }

  class Island
  {
    string name;
    int x, y, w, h;
    int population;
    vector<House> houses_on_island
  }

  class House
  {
    int x, y, w, h;
    string owner;
  }


I want to quickly retrieve the house coordinates and size so that I can draw rectangles for each on the screen. I thought about including some of the classes in Screen_Drawing, but then figured that was bad because I can use it with other projects and then those includes would be meaningless. I could build a series of get statements, and possibly pass an array by reference to be filled with the values, but that feels like the wrong approach. Any advice?
Hello opisop,

First I will suggest that you have a look at http://www.cplusplus.com/doc/tutorial/classes/ and http://www.cplusplus.com/doc/tutorial/templates/

It has been my experience that class definitions generally precede "main" or are contained in a header file with any functions of the class fully defined and implemented in a '.cpp" file.

Reading the links you will fin that everything between the {}s is "private" by default, so what you have means that everything between the {}s is inaccessible because it is considered "private" in the class. Another thing I noticed is that a class is finished when you put a semi-colon after the closing } of the class.

Even when you put the classes above main there is an order problem. When the compiler reaches the class "Map" everything is fine until the compiled tries to deal with the vector of type "Island". At this point the compiler has yet to learn about "Island" and will stop, most likely with the error, that "Island" is undefined. So you would need to make the order of the classes "House", "Island" and then "Map". so the compiler will learn about each in the proper order.

In "main" as you did with "Screen_Drawing screen;" you will need to do this with each class so that you have something to work with.

Since a function can only return one item you will need a "get" function for each variable that you want to use or maybe a function that puts each variable in a vector and return the vector.

When reading about classes pay attention to the "public" and "private" sections of the class.

You have a good start it just needs refined a bit.

Hope that helps,

Andy
> I want to quickly retrieve the house coordinates and size so that I can draw rectangles for each on the screen.

Just access it normally: for each island in the map, for each house on that island etc.
This would take very little time (the actual drawing time would probably be significantly more).

Less than five milliseconds to access rectangle information for a million houses:
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
#include <iostream>
#include <vector>
#include <random>
#include <ctime>

struct house { int x, y, w, h; };

struct island { std::vector<house> houses ; };

struct map { std::vector<island> islands ; };

// populate a map with NISLES islands, having NHOUSES houses each
map make_map( std::size_t NISLES, std::size_t NHOUSES )
{
    map the_map ;
    while( the_map.islands.size() < NISLES )
    {
        the_map.islands.emplace_back() ;
        while( the_map.islands.back().houses.size() < NHOUSES )
        {
            static std::mt19937 rng( std::random_device{}() ) ;
            static std::uniform_int_distribution distr( 10, 100 ) ;
            the_map.islands.back().houses.push_back( { distr(rng), distr(rng), distr(rng), distr(rng) } ) ;
        }
    }

    return the_map ;
}

long long test_it( const map& the_map ) // access h, w, x and y of every house
{
    long long result = 0 ;

    for( const island& isle : the_map.islands ) // for each island in the map
        for( const house& h : isle.houses ) // for each house on he island
            result += h.h + h.w + h.x + h.y ; // access the rectangle information 

    return result ;
}

int main()
{
    // make a ranom map with 1000 islands, each having 1000 houses
    // (one million houses in all)
    const map& test_map = make_map( 1000, 1000 ) ;

    const auto start = std::clock() ;
    const long long s = test_it(test_map) ;
    const auto end = std::clock() ;
    
    std::cout << "s == " << s << '\n'
              << (end-start) * 1000.0 / CLOCKS_PER_SEC << " milliseconds\n" ;
}

http://coliru.stacked-crooked.com/a/e05ca958f46274a9
you may want to consider putting the coordinates you need into a struct (or use the built in pair object) but this is really just window dressing. Its not worth a rewrite. It lets you move the coordinates as a single entity, is all, as x and y (and w and h as well) are all tightly coupled and should probably be glued together (if it were a more complex problem than what you have, that concept would be much more important). Since the only other item in there is the owner (not as tightly coupled for what you are doing) this gains nothing, but if you had 20 other fields down in house besides the owner, it would be worth it.... does that make sense?

Last edited on
closed account (Ezyq4iN6)
@Handy Andy
The "code" I wrote was meant as a quick pseudocode to try and illustrate my question as simple as possible without posting too much code from my real code. I realize that I left out many key parts of the classes, proper coding structure, etc. I appreciate you pointing that out and I'll try to do that in the future.

@JLBorges
Thanks for the detailed example! I should have been more detailed in my original question. While yes, these examples I gave could be held in a struct, imagine that each class was full of variables and member functions, both public and private. Now they wouldn't (I don't think) be as appropriate in a struct. Your example would make it a lot easier for me to grab the values I need, but how would I do the same thing if the variables I need were all classes instead and the variables I needed didn't makes sense to put in a struct? Here is a more detailed example, but it can only retrieve one house from one island:
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <iostream>
#include <string>
#include <vector>

struct Dimensions
{
  int x;
  int y;
  int w;
  int h;
};

class House
{

  Dimensions d;
  std::string name;

public:
  House()
  {
    d.x = 0;
    d.y = 0;
    d.w = 100;
    d.h = 100;
    name = "This House";
  }
  void get_house_info(std::string &house_name, Dimensions &d_arg)
  {
    house_name = name;
    d_arg = d;
  }
};

class Island
{
  std::vector<House> houses_on_island;

public:
  void add_house()
  {
    houses_on_island.push_back(House());
    int house_count = houses_on_island.size();
    if (house_count == 1)
      std::cout << "There is now " << house_count << " house, ";
    else
      std::cout << "There are now " << house_count << " houses, ";
  }
  void get_house_info(std::string &house_name, Dimensions &d)
  {
    houses_on_island[0].get_house_info(house_name, d);
  }
};

// map is a keyword, so Map is now Region.
class Region
{
  std::vector<Island> islands_in_region;

public:
  void add_island()
  {
    islands_in_region.push_back(Island());
    int island_count = islands_in_region.size();
    if (island_count == 1)
      std::cout << "There is now " << island_count << " island.\n";
    else
      std::cout << "There are now " << island_count << " islands.\n";
  }
  void add_house(int island_number)
  {
    int island_count = islands_in_region.size();
    if (island_number > (island_count - 1))
      std::cout << "That island doesn't exist, try again.";
    else
    {
      islands_in_region[island_number].add_house();
      std::cout << " located on island " << island_number << ".\n";
    }
  }
  void get_house_info(int island_number, std::string &house_name, Dimensions &d)
  {
    islands_in_region[island_number].get_house_info(house_name, d);
  }
};

class Draw_Something
{
public:
  void draw_stuff(std::string const &house_name, Dimensions const &d)
  {
    std::cout << "House name: " << house_name << "\n";
    std::cout << "Drawing house with properties: " << d.x << " " << d.y
              << " " << d.w << " " << d.h;
  }
};

int main()
{
  Region region;
  region.add_island();
  region.add_house(0);

  std::string house_name;
  Dimensions house_stats;
  int island_number = 0;
  region.get_house_info(island_number, house_name, house_stats);

  Draw_Something screen;
  screen.draw_stuff(house_name, house_stats);
  return 0;
}


@jonnin
I completely agree about the struct, but when using it in multiple classes with the same struct should I have it in a header file like my_structs.h and include it with each class, or for something that small should I just code it into each class? And would you put x, y, w, h all together as one struct, or as separate pairs? I plan on implementing what you said because my real code is going to be more complex. I only tried to write a super simple example that was meant as pseudocode more than anything. Also, let's say the variables were not "tightly coupled" as you say. How you would you then pass them, as opposed to having to deal with only one struct.
In an object oriented design, each object knows how to draw itself. So all you have to do is tell the map to draw itself. You don't want some high-level Draw class to know or care about the details of a low level class like a house.

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
class Map
  {
    string name;
    vector<Island> islands_on_map
    void draw() {
        for (Island &isle: islands_on_map) { isle.draw(); };
    }
  }

  class Island
  {
    string name;
    int x, y, w, h;
    int population;
    vector<House> houses_on_island
    void draw() {
        for (Houe &house : houses_on_island) { house.draw(); }
    }
  }

  class House
  {
    int x, y, w, h;
    string owner;
    void draw() { draw_rectangle(x,y,w,h); }
  }
> In an object oriented design, each object knows how to draw itself.
> So all you have to do is tell the map to draw itself.

In general, it is a bad idea to tightly couple an object with one specific context in which the object could be used.
MVC is perhaps the earliest architectural pattern that came out of this recognition: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
> Here is a more detailed example, but it can only retrieve one house from one island:

You could provide a mechanism for iteration over a collection of sub-objects
(iterate over islands in a region, houses in an island etc.).

For example:
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
#include <iostream>
#include <string>
#include <vector>

struct Rect { int x; int y; int w; int h; } ;

class House
{
  Rect dim ;
  std::string name_ ;

  public:

      const Rect& dimension() const { return dim ; }
      const std::string& name() const { return name_ ; }
      // ...
};

class Island
{
  std::vector<House> houses_on_island;
  std::string name_ ;
  // ...

  public:
      // ...
      using iterator = std::vector<House>::const_iterator ;
      using range = std::pair<iterator,iterator> ;
      range houses() const { return { houses_on_island.cbegin(), houses_on_island.cend() }; }

      const std::string& name() const { return name_ ; }

      // ...
};

class Region
{
  std::vector<Island> islands_in_region;

  public:
      // ...
      using iterator = std::vector<Island>::const_iterator ;
      using range = std::pair<iterator,iterator> ;
      range islands() const { return { islands_in_region.cbegin(), islands_in_region.cend() }; }

      // ...
};

class Draw_Something
{
    public:
      void draw( const Region& r )
      {
         const auto islands = r.islands() ;
         for( auto isle_iter = islands.first ; isle_iter != islands.second ; ++isle_iter )
         {
            std::cout << "island: " << isle_iter->name() << '\n' ;
            const auto houses = isle_iter->houses() ;
            for( auto house_iter = houses.first ; house_iter != houses.second ; ++house_iter )
            {
                std::cout << "\tHouse name: " << house_iter->name() << "\n";
                const auto d = house_iter->dimension() ;
                std::cout << "\tDrawing house with properties: " << d.x << " " << d.y
                          << " " << d.w << " " << d.h;
            }
         }
      }
};

int main()
{
  Region region;
  // populate Region

  Draw_Something renderer ;
  renderer.draw(region) ;
}

closed account (Ezyq4iN6)
@dhayden
That was the way I was going to do it originally, but like in JLBorges's example, I'm now trying to keep the drawing part out of the other classes.

@JLBorges
That's a really great example, thanks! I'm a little unsure about how to properly use iterators even after viewing your example. Are they much more efficient that the way I have it now (see code example below)? I am new to vectors (coming from arrays), so I'm having trouble figuring out the basics with iterators and why to use them.

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include <iostream>
#include <string>
#include <vector>

struct Rect
{
  int x, y, w, h;
};

class House
{
  std::string name;
  Rect r;

public:
  House(std::string const &name_arg,
        Rect const &r_arg)
        :
        name(name_arg),
        r(r_arg)
        {}

  void get_house_info(std::string &name_arg,
                      Rect &r_arg)
  {
    name_arg = name;
    r_arg = r;
  }
};

class Island
{
  std::vector<House> houses_on_island;

public:
  void add_house(std::string const &house_name, Rect const &r)
  {
    houses_on_island.push_back(House(house_name, r));
    int house_count = houses_on_island.size();
    if (house_count == 1)
      std::cout << "There is now " << house_count << " house ";
    else
      std::cout << "There are now " << house_count << " houses ";
  }
  void get_house_info(int const house_number, std::string &house_name, Rect &r)
  {
    houses_on_island[house_number].get_house_info(house_name, r);
  }
  int get_house_count()
  {
    return houses_on_island.size();
  }
};

// map is a keyword, so Map is now Region.
class Region
{
  std::vector<Island> islands_in_region;

public:
  void add_island()
  {
    islands_in_region.push_back(Island());
    int island_count = islands_in_region.size();
    if (island_count == 1)
      std::cout << "There is now " << island_count << " island.\n";
    else
      std::cout << "There are now " << island_count << " islands.\n";
  }
  void add_house(int island_number, std::string const &house_name, Rect const &r)
  {
    int island_count = islands_in_region.size();
    if (island_number > (island_count - 1))
      std::cout << "That island doesn't exist, try again.\n";
    else
    {
      islands_in_region[island_number].add_house(house_name, r);
      std::cout << "located on island " << island_number << ".\n";
    }
  }
  void get_house_info(int const island_number, int const house_number, std::string &house_name, Rect &r)
  {
    islands_in_region[island_number].get_house_info(house_number, house_name, r);
  }
  int get_island_count()
  {
    return islands_in_region.size();
  }
  int get_house_count(int island_number)
  {
    return islands_in_region[island_number].get_house_count();
  }
};

class Draw_Something
{
public:
  void draw_house(std::string const &house_name, Rect const &r)
  {
    std::cout << "House name: " << house_name << "\n";
    std::cout << "Drawing house with properties: " << r.x << " " << r.y
              << " " << r.w << " " << r.h << "\n\n";
  }
};

int main()
{
  Region region;
  region.add_island();
  region.add_house(0, "House 1", {0, 0, 100, 100});
  region.add_house(0, "House 2", {100, 0, 25, 25});
  region.add_island();
  region.add_house(1, "House 3", {200, 0, 50, 50});
  region.add_house(1, "House 4", {0, 300, 75, 75});

  Draw_Something screen;
  std::cout << "---------------------------------------------\n";

  int total_islands = 0;
  total_islands = region.get_island_count();
  if (total_islands == 0)
  {
    std::cout << "There are no islands!\n";
    return 0;
  }
  for (int i = 0; i < total_islands; i++)
  {
    int total_houses = 0;
    total_houses = region.get_house_count(i);
    if (total_houses == 0)
      std::cout << "There are no houses on island #" << i << "\n";
    else
    {
      for (int j = 0; j < total_houses; j++)
      {
        std::string house_name;
        Rect house_stats;
        region.get_house_info(i, j, house_name, house_stats);
        screen.draw_house(house_name, house_stats);
      }
    }
  }

  return 0;
}
> I'm a little unsure about how to properly use iterators even after viewing your example.

An alternative (without using iterators) would be to return a reference to the (const qualified) vector.


> Are they much more efficient that the way I have it now (see code example below)?

Since you stated that performance is important ('I want to quickly retrieve the house coordinates and size so that I can draw rectangles for each on the screen'), it may be worthwhile to avoid copying the house information for each house. (It would be faster to directly access the information from the vector.)

For example:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <iostream>
#include <string>
#include <vector>

struct Rect { int x; int y; int w; int h; } ;

class House
{
  Rect dim_ ;
  std::string name_ ;

  public:

      House( std::string name, const Rect& dim ) : dim_(dim), name_( std::move(name) ) {}

      const Rect& dimension() const { return dim_ ; }
      const std::string& name() const { return name_ ; }
      // ...
};

class Island
{
  std::vector<House> houses_on_island;
  std::string name_ ;
  // ...

  public:
      // ...

      Island( std::string name ) : name_( std::move(name) ) {}

      const std::vector<House>& houses() const { return houses_on_island ; }

      const std::string& name() const { return name_ ; }

      void add_house( House h ) { houses_on_island.push_back( std::move(h) ) ; }

      // ...
};

class Region
{
  std::vector<Island> islands_in_region;

  public:
      // ...
      const std::vector<Island>& islands() const { return islands_in_region ; }

      void add_island( Island isle ) { islands_in_region.push_back( std::move(isle) ) ; }

      // ...
};

class Draw_Something
{
    public:
        void draw( const Region& r )
        {
            // range based loop : http://www.stroustrup.com/C++11FAQ.html#for
            for( const auto& isle : r.islands() ) // for each island isle in the region
            {
                std::cout << "\nisland: " << isle.name() << '\n' ;
                for( const auto& h : isle.houses() ) // for each house h in isle
                {
                    std::cout << "\tHouse: " << h.name() << "\n";
                    const auto d = h.dimension() ;
                    std::cout << "\t\tDrawing house with dimension: " << d.x << ' '
                              << d.y << ' ' << d.w << ' ' << d.h << '\n' ;
                }
            }
        }
};

int main()
{
  Region region;

  // populate Region
  {
      // add first island
      Island isle( "Island zero" ) ;
      isle.add_house( { "House 1", {0, 0, 100, 100} } ) ;
      isle.add_house( { "House 2", {100, 0, 25, 25} } ) ;
      region.add_island( std::move(isle) ) ;
  }

  {
      // add a second island
      Island isle( "Island one" ) ;
      isle.add_house( { "House 3", {200, 0, 50, 50} } ) ;
      isle.add_house( { "House 4", {0, 300, 75, 75} } ) ;
      region.add_island( std::move(isle) ) ;
  }

  Draw_Something renderer ;
  renderer.draw(region) ;
}

http://coliru.stacked-crooked.com/a/6dc13b3e906eabbe
closed account (Ezyq4iN6)
Thank you again for the very detailed answer, and the fact that it reduces the number of lines.

I have never seen std::move() used before. I looked up some information about it, but it just looks like an alternative to passing by reference, or something similar. What exactly is the benefit of using it vs. another method of not passing by copy?
The old (2002) proposal for move semantics explains the rationale for move semantics in an easily understandable way:
Motivation
Move semantics is mostly about performance optimization: the ability to move an expensive object from one address in memory to another, while pilfering resources of the source in order to construct the target with minimum expense.
...
Copy vs Move
C and C++ are built on copy semantics. This is a Good Thing. Move semantics is not an attempt to supplant copy semantics, nor undermine it in any way. Rather this proposal seeks to augment copy semantics. A general user defined class might be both copyable and movable, one or the other, or neither.

The difference between a copy and a move is that a copy leaves the source unchanged. A move on the other hand leaves the source in a state defined differently for each type. The state of the source may be unchanged, or it may be radically different. The only requirement is that the object remain in a self consistent state (all internal invariants are still intact). From a client code point of view, choosing move instead of copy means that you don't care what happens to the state of the source.

For PODs, move and copy are identical operations (right down to the machine instruction level).
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm
closed account (Ezyq4iN6)
Interesting. I'll practice with some examples from that link and see if I can make all of that sink in.

Thanks to everyone who helped with my question!
Topic archived. No new replies allowed.