5

The question seems very abstract. I will ask by an example instead.

Introduction

Assume that I have many types of game objects.

They are Bullet, Rocket, Enemy, Zone, ... .

They are all nicely created & deleted & managed by pools, e.g.

Pool<Bullet> poolBullet ; 
Pool<Rocket> poolRocket ;  

The game logic would manage object in form of Pool_Handle< xxx >, e.g.

Pool_Handle< Bullet >  bullet = poolBullet.create() ;
Pool_Handle< Rocket>  rocket = poolRocket .create() ;

Problem

Now, I look at the refactoring.

For example, if my old code was ...

Bullet* functionA(Rocket* rocket){
    for(int n=0;n<rocket->zones.size();n++){
        Zone* zone = rocket->zones.get(n);
        Bullet* return_value =zone.functionB();
    }
}

... it would become ...

Pool_Handle<Bullet> functionA(Pool_Handle<Rocket> rocket){
    for(int n=0;n<rocket->zones.size();n++){
        Pool_Handle<Zone> zone = rocket->zones.get(n);
        Pool_Handle<Bullet> return_value =zone.functionB();
    }
}

Notice that they are now Pool_Handle everywhere.

After reading and editing these thousands line codes for a few days, I have grown accustomed to Pool_Handle even more than any game objects. It might be a word that I type fastest now.

Question

How to retain readability and maintainability to equal level of the old code, and if possible, how to reduce time of typing for the variable template?

I don't expect a perfect answer, because every solution I found has some trade-off.

My poor solutions

/**  remedy 1 **/
template <class TT> using H = Pool_Handle<TT>; //remedy line
H<Bullet> bullet ; //alleviate symptom, better but not perfect

/**  remedy 2 **/
using H_Bullet  = Pool_Handle<H_Bullet>; //remedy line
H_Bullet bullet ; //looks good, but have limitations

/** remedy 3 **/
auto bullet;  //concise, but can't be used everywhere, sometimes also reduce readability

The second solution seems to be nice, but introduces several limitations.

  • I have to declare a line for Bullet, Rocket, ... and so on, one by one

  • If there is a new type of game object, I will have to add another line manually.

  • If a game object is renamed, e.g. Bullet -> MetalBullet, I will also have to change the remedy line to H_MetalBullet manually.

  • Therefore, it lowers overall maintainability.

Are there any better approaches?

(Edit) You can assume c++11, c++14, whatever.
(Edit) A platform independent solution is favorable.

Edit 1 (To clarify even more why the second solution is not perfect)

In the second solution, I have to add "another" line after declare a new class.

The line is ...

using H_Bullet  = Pool_Handle<H_Bullet>; 

Where should I place it?

  1. inside Bullet.h ;

    It will create bad coupling, because Bullet should not know about the Handle at all.

  2. inside some high-level header that is included by every game logic files ;

    The result is that there are 2 different places that has some definitions about Bullet.
    More places -> Less maintainability.

Both places will yield a certain minor disadvantage : When I call some automatic refactoring I have to call twice, on both Bullet and H_Bullet.

Community
  • 1
  • 1
javaLover
  • 6,347
  • 2
  • 22
  • 67
  • 8
    To reduce time of typing you should use an IDE with autocompletetion. Refactoring the code with the aim of having less to write is kind of paradox and can lead to most obfuscated code. – 463035818_is_not_an_ai Jun 05 '16 at 14:13
  • I don't find that Visual studio has any features to help with this complex-and-subjective problem. (rename Bullet -> MetalBullet and auto rename H_Bullet->H_MetalBullet in one dialog block) Which IDE do you recommend? I am very glad to hear out. – javaLover Jun 05 '16 at 14:17
  • 3
    I don't really understand your issues with the second solution. You have to declare a new class, well, every time you declare a new class. If you have a new type of game object you will still have to declare a new class. And, if in the old code, you renamed a game object you would've still had to rename every reference to it. – Sam Varshavchik Jun 05 '16 at 14:18
  • Hi javaLover, welcome to SO. For better formatting, prefer Headings (CTRL-H) for titles/paragraphs. – edmz Jun 05 '16 at 14:19
  • 8
    [`auto` specifier](http://en.cppreference.com/w/cpp/language/auto) – Ivan Aksamentov - Drop Jun 05 '16 at 14:22
  • @javaLover i was only refering to autocompletion. To avoid complex search and replace using typedefs can help to some extend. – 463035818_is_not_an_ai Jun 05 '16 at 14:24
  • `using BulletHandle = Pool_Handle;` – Ivan Aksamentov - Drop Jun 05 '16 at 14:25
  • @Sam Varshavchik ; I will edit the question to clarify it. .... To "black" , sorry and thank, I will remember. – javaLover Jun 05 '16 at 14:30
  • 3
    This question may belong to [codereview.stackexchange.com](https://codereview.stackexchange.com) – rubik Jun 05 '16 at 14:35
  • 1 header with every using inside. Like PooledTypes.h. If change class name, u just change types inside this header. Overcomplicating is bad too. Your points in solution 2 is nothing. You just spent a lot of time to solve standart problem, and solution is single header with typedefs inside. – Denis Ermolin Jun 05 '16 at 15:47

3 Answers3

2

Option 1

Using the auto specifier:

auto object = pool.create();

Complete example:

template <typename T>
class Pool {
public:
  class Handle {};

  Handle create () const {
    return Handle();
  }
};

class Object {};

int main () {
  Pool<Object> pool;
  auto object = pool.create();
}

View Successful Compilation Result

Option 2

Using a typedef (for those who do not have access to features):

Object::Handle object = pool.create();

Complete example:

template <typename T>
class Pool {
public:
  class Handle {};

  Handle create () const {
    return Handle();
  }
};

class Object {
public:
  typedef Pool<Object>::Handle Handle;
};

int main () {
  Pool<Object> pool;
  Object::Handle object = pool.create();
}

View Successful Compilation Result

Binary Birch Tree
  • 15,140
  • 1
  • 17
  • 13
  • @ Binary Birch Tree ; Thank .... Your first solution is same as the third in my poor solution. Your typedef solution is unique (+1) but suffer the same disadvantage as my second solution (using H_Bullet). I still have to add some lines, for each game object, one by one, and rename still hard. If I am wrong, please fix me. – javaLover Jun 05 '16 at 14:51
2

Besides using auto, you could also benefit from using STL algorithms instead of hand-written loops. In your example, instead of

Pool_Handle<Bullet> functionA(Pool_Handle<Rocket> rocket){
    for(int n=0;n<rocket->zones.size();n++){
        Pool_Handle<Zone> zone = rocket->zones.get(n);
        Pool_Handle<Bullet> return_value =zone.functionB();
    }
}

You can just use std::for_each

std::for_each( rocket->zones.begin(), rocket->zones.end(), 
               [](auto const& z) {z.functionB();} );

For this simple case, the C++11 for-each loop is probably even better:

for(auto const& z: rocket->zones) {
    z.functionB();
}

In general, I would strongly recommend to look at a range library, e.g. Boost.Range or Eric Niebler's range-v3. This enables you to write very condensed but descripitve code, e.g.

// call functionB on all zones and sum the results
auto sumOfResult = accumulate( rocket->zones | transformed( [](auto const& z) { return functionB(z);}) );
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Jens
  • 9,058
  • 2
  • 26
  • 43
  • I believe you forgot a closing bracket in the last example. Also, the `|` in `rocket->zones | transformed(...)` looks very weird. – ForceBru Jun 05 '16 at 15:39
  • @ForceBru Added the missing parentheses. The `|` is the operator for chaining ranges with adaptors in Boost.Range. It is similar to chaining streams on the shell, e.g. cat file|grep pattern|wc -l. I think it is quite readable. Eric Niebler calls them views and uses the same operator. I think this will be added to the standard in the future. – Jens Jun 05 '16 at 15:44
  • this is interesting, have an upvote! – ForceBru Jun 05 '16 at 15:53
  • It would be great if auto const& can be explicit type, because sometimes "auto" destroys readability. Although the condense code technique reduces readability and increase compile time because of additional library, it is an interesting and cool concept, thank! +1 – javaLover Jun 05 '16 at 16:06
1

What's wrong with a header that just forward declares the types you're interested in? It will only change when you add new types to your system that need handles, and if you rename / add something it will break gracefully.

types_fwd.hpp

template <typename> Pool;
template <typename> Pool_Handle;

class Bullet;
class Rocket;

using H_Bullet = Pool_Handle<Bullet>;
using H_Rocket = Pool_Handle<Rocket>;

The only other option would be to invert things and forward declare the pool and handle and include / add that to each header that introduces new types that need handles.

Brandon
  • 724
  • 5
  • 12