Code Trip
  • Blog
  • Archive
  • Projects
  • Portfolio
  • CV

Year 3, Semester 1 - ClassGen: generating .h and .cpp files for new C++ classes

16/2/2015

0 Comments

 
September 2014 - December 2014

Click to download a .zip file containing the command-line executable, example outputs and user guide.

Features

For my optional module I chose Games Tool Programming, wherein we had to create a tool to aid in some stage of the games production pipeline. As a programmer I decided to create a tool to aid in the code creation process so that I could use it myself.
If you're an experienced C++ programmer, the following ritual will be very familiar to you: creating .h file; declaring the class; adding a header guard; declaring methods; adding private members; adding getters and setters to access those private members; creating a .cpp file; defining methods; and so on and so forth. The goal of the ClassGen language and compiler is to automate the monotonous aspects of the class creation process as much as possible, freeing the programmer to focus on more important design and implementation details.
The current features of ClassGen include:
  • Multiple class declarations per ClassGen script. Each class has separate .h and .cpp files generated for it.
  • Copyright and author comments at the tops of all files.
  • Descriptive comments at the top of .h files.
  • Descriptive comments immediately preceding the class declaration.
  • Appropriately named header guards.
  • #include statements in the .h file. Supports both "file" and <file> syntaxes.
  • Single base class inheritance.
  • Constructors, destructors, methods and fields. Default constructors are always generated.
  • Hierarchical namespaces (e.g. Engine::Graphics::Sprite).
  • Access levels (public/protected/private). Access levels are supported both for inheritance and class members.
  • Descriptive comments immediately preceding a field declaration in the .h file.
  • Descriptive comments immediately preceding a constructor, destructor or method definition in the .cpp file.
  • Assigning a default value to a member field in the default constructor.
  • Assigning an argument's value to a member field in a user-defined constructor.
  • Constant, pointer and reference types.
  • Generating inline Getter and/or Setter functions for a field.

Worked examples

So to start with, the programmer creates a script in the ClassGen language. The most minimal script would look something like this:
1
2
class MyClass {
}
This would produce my_class.h and my_class.cpp. The header file contains:
1
2
3
4
5
6
7
8
9
#ifndef MY_CLASS_H_
#define MY_CLASS_H_

class MyClass {
public:
    MyClass();
};

#endif // MY_CLASS_H_
And the body file:
1
2
3
4
5
#include "my_class.h"

MyClass::MyClass() {
   
}
Note that the names of the files and the header guard are based on the name of the class. 
A public default constructor was automatically added to the class declaration, with an accompanying stub in the body file (if I continue development of ClassGen I will likely remove this or make it optional).
A more fully featured script would be something along the lines of:
 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
copyright "My Company Name 2014"
author "Alice Brown"

%% The GameObject class should be used for anything that the player can interact
%% with in the world. GameObjects handle collision detection, among other things.
include "texture.h", "sprite.h", "collision_mask.h"
include <cstdio>
// Since GameObjects are interactable they should be visible, so this class inherits
// from Sprite. GameObjects are guaranteed a unique ID and may also be given a name.
class Engine::Game::GameObject : Graphics::Sprite {
    field char* m_name{NULL} with getter, setter
   
    // Runs collision detection and response against all other objects in the world.
    method void CheckForCollisions(World& world)
    // @returns true if this object is colliding with the given one.
    private method bool IsColliding(GameObject& object)
   
    field Game::CollisionMask m_collision_mask with setter
    field bool m_dead with getter
   
    destructor
   
    // Loads in the texture and sets up the object, including ID.
    // @returns true if successful, false if an error occurred.
    method bool Initialize(const char* texture_name)
   
    // If this is -1 then it means that the object hasn't been
    // properly initialized yet.
    protected field int m_id{-1} with getter
}

include "renderable.h", "color.h", <cstdio>
// A set of characters which can be rendered to the screen.
class Graphics::Text : protected Graphics::Renderable {
    field char* m_text{NULL} with setter
   
    field Graphics::Color m_color{255, 255, 255, 255} with setter
   
    constructor(char* text : m_text, Graphics::Color& color : m_color)
    destructor
   
    // Renders the text to the given window.
    method void Render(Graphics::Window& window)
}
This script demonstrates every feature currently in ClassGen. At the top of the script are copyright and author comments, followed by multiple file comments and several includes (using both multiple include statements and comma-separated lists). This is then followed by multiple class comments and the class declaration itself, which features hierarchical namespaces and inheritance (with no specified access level, meaning it will default to public).
The first class member is a field with a pointer type, default value, getter and setter. The second is a method with a comment and an argument with a reference type. The third member is another method, this one with a specified access level. Following on are more field declarations (with a namespaced type) and a destructor. Finally there are two more members; a method with multiple comments and a constant argument type; and lastly another field, with multiple comments and a specified access level.
The second class declaration has a specified access level for the inheritance and a more complex default value for one of the fields. It also demonstrates a constructor with arguments which will be used to initialize specified fields.
Processing this script with ClassGen produces 4 files: game_object.h, game_object.cpp, text.h and text.cpp. These are shown below. game_object.h:
 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
// (c) My Company Name 2014
// Author: Alice Brown

// The GameObject class should be used for anything that the player can interact
// with in the world. GameObjects handle collision detection, among other things.

#ifndef GAME_OBJECT_H_
#define GAME_OBJECT_H_

#include "texture.h"
#include "sprite.h"
#include "collision_mask.h"
#include <cstdio>

namespace Engine {

namespace Game {

// Since GameObjects are interactable they should be visible, so this class inherits
// from Sprite. GameObjects are guaranteed a unique ID and may also be given a name.
class GameObject : public Graphics::Sprite {
public:
    GameObject();
    ~GameObject();
   
    void CheckForCollisions(World& world);
    bool Initialize(const char* texture_name);
   
    inline char* GetName() const;
    inline bool IsDead() const;
    inline int GetId() const;
   
    inline void SetName(char* value);
    inline void SetCollisionMask(Game::CollisionMask value);
   
protected:
    // If this is -1 then it means that the object hasn't been
    // properly initialized yet.
    int m_id;
   
private:
    bool IsColliding(GameObject& object);
   
    char* m_name;
    Game::CollisionMask m_collision_mask;
    bool m_dead;
};

inline char* GameObject::GetName() const {
    return m_name;
}

inline bool GameObject::IsDead() const {
    return m_dead;
}

inline int GameObject::GetId() const {
    return m_id;
}

inline void GameObject::SetName(char* value) {
    m_name = value;
}

inline void GameObject::SetCollisionMask(Game::CollisionMask value) {
    m_collision_mask = value;
}

}   // namespace Game

}   // namespace Engine

#endif   // GAME_OBJECT_H_
The file starts off with the copyright and author comments, followed by the file comments, header guard, #includes and namespaces. This is followed by the class comments and the actual class declaration itself, with inheritance at the default public access level.
Within the class declaration are the members, but they are not in the order they were found in the script. Members are sorted first by access level (public first, then protected, then private), and within an access level they follow the order of constructors, destructors, methods, getters, setters and finally fields. The order of members relative to each other is preserved; for example the method CheckForCollisions was declared before the method Initialize in the script and so it comes first in the set of public method declarations.
Starting with the public members: a default constructor has automatically been added along with the destructor declared in the script; then come the public methods and finally the generated getter and setter functions. The names of these getters and setters are based on the fields they relate to; for example the field m_name had the functions GetName and SetName generated for it. ClassGen  supports starting member names with m_ and ignores it when naming getters and setters. The getter for the boolean member m_dead has been named IsDead, rather than GetDead.
The protected members consist of a single field with multiple accompanying comments. The private members comprise a private method followed by the remaining field declarations, again matching the relative order they were declared in in the input script.
With the class declaration complete, the definitions of the getter and setter functions come next. These match the order they were declared in. Finally the file ends with closing braces for the namespaces and a closing #endif for the header guard.
game_object.cpp contains:
 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
// (c) My Company Name 2014
// Author: Alice Brown

#include "game_object.h"

Engine::Game::GameObject::GameObject()
  :   m_id(-1),
      m_name(NULL) {
   
}

Engine::Game::GameObject::~GameObject() {
   
}

// Runs collision detection and response against all other objects in the world.
void Engine::Game::GameObject::CheckForCollisions(World& world) {
   
}

// Loads in the texture and sets up the object, including ID.
// @returns true if successful, false if an error occurred.
bool Engine::Game::GameObject::Initialize(const char* texture_name) {
   
}

// @returns true if this object is colliding with the given one.
bool Engine::Game::GameObject::IsColliding(GameObject& object) {
   
}
This file once again starts with the copyright and author comments, followed by a #include for the accompanying header file. The rest of the file is the function stubs matching their declaration order. The default constructor is first and assigns the default values to those fields which specified one. The rest of the function stubs simply define their functions with appropriate return types and arguments. The comments for constructors, destructors and methods immediately precede them here in the body file, rather than cluttering up the header file.
text.h is as follows:
 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
// (c) My Company Name 2014
// Author: Alice Brown

#ifndef TEXT_H_
#define TEXT_H_

#include "renderable.h"
#include "color.h"
#include <cstdio>

namespace Graphics {

// A set of characters which can be rendered to the screen.
class Text : protected Graphics::Renderable {
public:
    Text();
    Text(char* text, Graphics::Color& color);
    ~Text();
   
    void Render(Graphics::Window& window);
   
    inline void SetText(char* value);
    inline void SetColor(Graphics::Color value);
   
private:
    char* m_text;
    Graphics::Color m_color;
};

inline void Text::SetText(char* value) {
    m_text = value;
}

inline void Text::SetColor(Graphics::Color value) {
    m_color = value;
}

}   // namespace Graphics

#endif   // TEXT_H_
Things of note for this file are the inheritance access level as specified in the input script, and the constructor with arguments (in addition to the automatically added default constructor). The accompanying body file (text.cpp) consists of:
 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
// (c) My Company Name 2014
// Author: Alice Brown

#ifndef TEXT_H_
#define TEXT_H_

#include "renderable.h"
#include "color.h"
#include <cstdio>

namespace Graphics {

// A set of characters which can be rendered to the screen.
class Text : protected Graphics::Renderable {
public:
    Text();
    Text(char* text, Graphics::Color& color);
    ~Text();
   
    void Render(Graphics::Window& window);
   
    inline void SetText(char* value);
    inline void SetColor(Graphics::Color value);
   
private:
    char* m_text;
    Graphics::Color m_color;
};

inline void Text::SetText(char* value) {
    m_text = value;
}

inline void Text::SetColor(Graphics::Color value) {
    m_color = value;
}

}   // namespace Graphics

#endif   // TEXT_H_
This final file demonstrates a user-defined constructor which initializes fields using the supplied arguments. The input script of 44 lines has been interpreted into 168 lines of C++ code across 4 different files.

Improvements

There are obviously many features of C++ which are not currently supported by ClassGen such as arrays and container types, enums, virtual and pure virtual functions, inline functions, default argument values, structs, nested classes/structs etc. Obviously adding support for these features would make the tool more robust, but the large number of currently supported features is good enough in many cases and provides a start in many more.
Other features such as automatically deleting and nullifying pointer members in a class' destructor, being able to #include files in the body file, and using namespaces would also improve the program.
But by far the biggest limitation of ClassGen as it stands is coding style. The program only supports one coding style, and it's one I myself already have problems with (getters and setters after the class declaration?). If I were to continue development of ClassGen, the first thing I would add would be a style sheet that can be passed as an optional parameter to the executable and which would dictate the coding style of the generated artifacts. This could include:
  • The tab size to use, and whether to use tabs or spaces.
  • The directory to save generated artifacts to.
  • The file extensions to use (.h/.hh/.hpp/.hxx/.cc/.cpp/.cxx and so on).
  • Where to define inline getters and setters.
  • The naming convention to use for files, getters and setters.
  • What style of comments to use (this could even be extended to include support for third party tools such as Doxygen).
  • The placement of opening and closing braces, brackets and parentheses.
  • The order of access levels in the class declaration, and whether to have separate access levels for methods and fields.
  • The order that member types appear within an access level.
  • A maximum line length.
  • Etc.

Download

If you want to play around with ClassGen yourself or read a more comprehensive discussion of the scripting language, you can download .zip file containing the command-line executable, example outputs and user guide.
0 Comments

    Author

    Connor Halford. Studied Computer Games Technology at Abertay, worked as a Games Programmer at MediaTonic, now working as a Programmer at Climax Studios.
    ​

    Useful Sites

    hilite.me converts source code into formatted, embeddable HTML without the need for CSS or Javascript.

    tablesgenerator.com performs a similar task as hilite.me but for tabular data.

    Archives
    All posts

    June 2017
    December 2016
    September 2016
    August 2016
    June 2016
    May 2016
    April 2016
    February 2016
    January 2016
    October 2015
    September 2015
    August 2015
    June 2015
    May 2015
    March 2015
    February 2015
    January 2015
    December 2014
    September 2014
    August 2014
    July 2014
    March 2014
    February 2014
    August 2013
    June 2013
    December 2012

    Categories

    All
    Advice
    AI
    Algorithms
    AMPS
    Audio
    Boost
    Box2D
    Coursework
    DirectX
    Flash
    Game Boy Advance
    Game Jam
    Graphics Programming
    Honours Project
    Maths
    Nonograms
    Oh God Why
    OpenGL
    PICO-8
    Pixel Art
    PlayStation 4
    PlayStation Vita
    Procedural Generation
    SFML
    Shaders
    Spirit Shift
    Twine
    Unity
    XAudio2
    Year 1
    Year 2
    Year 3
    Year 4

    RSS Feed

Powered by Create your own unique website with customizable templates.