Transitioning from GML to C++

From GMpedia.org Wiki

Jump to: navigation, search
This article (or section) may need to be wikified to meet GMpedia's quality standards.
Please help improve this article, especially its categories, and wiki-links.

This tutorial works with...

Contents

[edit] Introduction

GML is a great entry point into the world of programming and can even be useful to advanced programmers when prototyping code. However, any budding computer scientist, programmer, or [computer] engineer will eventually want to invest their time in a language which inherently makes you a better programmer. Languages such as GML have a wonderful balance of power and ease, but if you only learn GML you may never encounter such concepts as static typing, pointers, and polymorphism which are mostly masked by the high-levelness of GML. In this article, I will explore transitioning from the high-level language GML to the low-level language C++. Please note that this is not an introduction to C++, nor is it a C++ tutorial; rather, it should be thought of as a simple outline of the differences between C++ and GML. This article assumes you have a decent background in GML and programming in general.

[edit] Why GML?

There are several fundamental reasons why I chose GML as the low level language to start off with. For starters, I have a certain proficiency in GML whereas I am only an amateur in other similar languages. Secondly, it is an interpreted language meaning it is never compiled down to machine code (as opposed to C++ which is a compiled language). Thirdly, it is inherently insecure and is always venerable to certain forms of code injection, total decompilation (to the original source) and other attacks; this makes it important for others to at least understand the fundamentals of what is happening under the surface so that they can understand the risks involved when using GML for any project which may contain sensitive data (note that Breakout and Street Racer clones do not constitute sensitive data). Also, because GML's IDE supplements GML with drag and drop actions, much of GML's user-base is completely new to the world of programming and are often inexperienced. These users may be interested in continuing their education in the computer sciences, and thus that large user-base translates directly into potential readers. Finally, GML often has similar syntax to C-family languages, is (almost) object-oriented, and contains many features which are similar to the feature set of C++ such as object inheritance. Therefore it is an ideal starting point when transitioning to C++. While that is all well and good it begs the question: “Why C++?”

[edit] Why C++?

First of all, I like C++, and have some small knowledge of the ins and outs of the language. Secondly, it is (normally) a compiled language, meaning it is translated down to machine code by a program called a “compiler”, at what is known as “compile-time”. And thirdly it is only as secure as the user makes it; which is wonderful as it forces one to do well or face the Rancor consequences. And finally, it has all the hard-parts which don't need to be there left in and has a very strict syntax (as opposed to GML's very flexible syntax). In short, GML and C++ look like identical twins whose personalities are polar opposites.

[edit] A Note About Source Code

Applications written in GML are very rarely entirely written in GML. And this is a good thing; GML is highly integrated with the Game Maker IDE, and for this reason resources are not normally defined in code. Objects, sprites, sounds, and other files are added through the IDE in a fully graphical way. In C++, you cannot always expect this, and this is a good thing too. There are environments out there which will give you graphical means of creating your program and then output the C++ source, but traditionally C++ is a fully coded language. This means that you cannot simply click "Add Object" in C++ and make it so, nor can you simply choose the "Create", or "Draw" events and have code executed in them, in fact, this makes no sense as “C++” is not a program or an IDE, it is a language, an abstract concept. You will have to program your application fully, by yourself, with (most of the time) no help from the environment which could just as easily be notepad as a graphical IDE. Also, you must understand that applications written in Game Maker normally have only one source file, the GMK file, while applications in C++ may have many source files working together. So to recap: in C++ objects and resources must be defined in the code as opposed to in a nice user interface, and the code will most likely be spread out across multiple source and resource files.

[edit] Compilers

When programming in C++, your compiler is your best friend. It is what will build your program, converting it from an abstract description to a hardware-layer command set. There are many free and commercial compilers available on the internet and I would recommend getting one that includes an IDE as it will make your life much easier. A good starting point might be the NetBeans IDE which can be optionally downloaded with C++ support and can be run under Windows, Linux, Solaris, and Mac OS X. Be sure to download a compiler if you choose NetBeans, otherwise you just have a fancy text editor. I would recommend one of the GNU compilers such as MinGW. Other good free products include Digital Mars (compiler only) and Visual C++ Express (IDE and compiler). Before you continue you should be sure to be comfortable with your development environment. After this point you will be expected to know how to compile a simple console application. All of the programs in this tutorial will be console based programs, meaning that they will use text to communicate with the end user.

[edit] Two Sample C++ Programs

Most C++ tutorials start out by showing you a basic C++ program; this article is no different. So fire up your editor of choice, create a new source file with a creative name like "CPPExample.cpp" and enter the following:

#include <iostream>
using namespace std;
int main(void)
{
 cout << "This is a test of the emergency broadcast system.\n";
 cout << "In the event of a real emergency, this would be a hello world example.\n";
 cout << "Hello World.\n";
 return 0;
}

Now compile your program (see your compilers manual for specific instructions) as an executable, fire up the command prompt, navigate to the directory of your program, and run it. You should see the following output:

This is a test of the emergency broadcast system.
In the event of a real emergency, this would be a hello world example.
Hello World.

Congratulations, you've just compiled a C++ program (which was, unfortunately for you, functionally worthless)! Now let’s look at a more complex example which actually serves some purpose:

#include <iostream>
using namespace std;
// An object called kelvin
class kelvin
{
 public:
 double value; // a variable called value (should probably not be public)
 kelvin()
 {
  value = 0;
 }
 double toCelsius()
 {
  return (value - 273.15);
 }
 double toFahrenheit()
 {
  return (this->toCelsius() * 9/5 + 32);
 }
};
// A function called main()
int main(void)
{
 kelvin temperature;
 cout << "Enter a value in kelvin: ";
 cin >> temperature.value;
 cout << " kelvin is equal to:\n";
 cout << "\t" << " degrees Celsius.\n";
 cout << "\t" << " degrees Fahrenheit.\n";
 return 0;
}

This program is a bit complex for what it does (in fact, the whole program is completely over-engineered, but for the purposes of demonstration it works fine). Basically, we have created a class (in GML this would be called an object) called "kelvin", we have then created a function called main(). The function then creates an instance of the class kelvin (and stores it inside the variable 'temperature'), gets some value for it, and converts the value the user entered into degrees Celsius and degrees Fahrenheit and outputs the results. As you can see, C++ is much different than GML. Now lets explore some of their differences starting with a simple variable.

[edit] Variables

Variables in C++ are much like variables in GML with a few minor exceptions. First of all, variables in C++ must be declared before we can use them. That means we can not simply set the variable x equal to 0 any time we want, instead, we must first describe the variable so that your program can create it. Declaring a variable involves two things, describing the variables type (which we'll get to in a moment) and telling the compiler what the variables name is. Just like in GML variables in C++ can consist of letters, numbers, and the underscore character, and must start with a letter. It is important to note that, like GML, C++ is case sensitive meaning that "Davros" is treated as something completely different from "davros". After variables have been declared, they can be referenced just like variables in GML. A sample variable declaration might look like this:

int x;

You may also assign a value to a variable when you define it, or wait until after it is assigned, assign multiple variables at once, and set multiple variables equal to something at once so the following is valid in C++:

int x = 5;
int y, z;
y = z = x;

The first line of the above creates a variable called "x" and assigns it a value of 5. The next line creates two variables called y and z, and the final line assigns y and z the value that was in x; therefore x, y, and z are equal to 5. As you can see, variables look about the same as they do in GML, but you must define them by specifying a type and you can define multiple variables at once (like you might do with the var or globalvar keywords in GML). Unlike GML, in C++ you can also define variables using what is known as constructor initialization, but this is rarely used in practice. The following two initialization methods are equivalent:

int x = 255;
int x (255);

Now what about that weird "int" that keeps popping up?

[edit] Types

In many languages variables have a type. That is, they store a specific type of data, be it a string, an integer, a character, an instance of an object, or some other data (GML only supports two types, strings and what C++ calls "doubles", that is, double precision floating point numbers, but we'll get to that in a minute). Functions, though you may not have known it, also have a type called a "return type" which means exactly what it sounds like: the type of data which the function returns (in the example above, the functions return type is "int"). The reason you might not be familiar with types is that GML uses what is called "dynamic typing". This means that GML automatically allocates memory for whatever you store in a variable, if you set x equal to 10, and then set it equal to "ten" a minute later, GML automatically changes its type from double to string. C++, however, is a bit more picky than that. C++ requires that for all functions and variables we provide a type, and, for the most part, stick with that type. So unlike GML, we cannot create a variable called x and assign it an integer and a string, we must pick one or the other when we declare the variable (as seen in the last section). In a declaration (be it for a function or variable) the type comes before the name. Here are a few common C++ types:

  • int - Stores 4 bytes (signed: -2147483648 to 2147483647, unsigned: 0 to 4294967295)
  • long (or, "long int") - Same as "int"
  • short (or, "short int") - Stores 2 bytes (signed: -32768 to 32767, unsigned: 0 to 65535)
  • char - Stores one byte (a character such as 'a', or an integer between 0 and 255 unsigned and -128 to 127 signed)
  • bool - Stores a boolean value (true or false)
  • float - 4 byte floating point number (3.4e +/- 38, 7 digits)
  • double - 8 byte (double precision) floating point number (1.7e +/- 308, 15 digits)

Note that the sizes and ranges provided here are actually system specific (most systems have 8 bits in a byte, but some have more or less). You may also notice that the value of some types changes based on whether it is signed or unsigned. This simply means whether or not it can store negative numbers; the default is signed. To declare a variable unsigned or if you just want to explicitly state that it is signed you simply append signed or unsigned to the type like so (note that char is considered a different fundamental data type than signed char or unsigned char, in general practice you should use char to store characters and signed char and unsigned char to store numbers):

unsigned char x = 255;
signed int y = -2147483648;

It is worthwhile to note that there is no fundamental string data type in C++. Rather, strings are represented either as literals, surrounded by quotation marks (e.g. "This is a string"), or by an array of chars. It should also be noted that GML does not have a char data type, instead it just has a string data type and thus single quotes () and double quotes ("") both represent strings. In C++, however, this is not the case. Single quotes can only contain a single character and represent a literal of the char type (eg. 'c' or 'b') and double quotes represent a string literal (e.g. "pi is approximately 3.14").

[edit] Type Casting

With so many types in C++, it would be nice if we could convert between them. If we set y equal to x, it would work fine since x is within the range of y. This is called implicit conversion. However, setting x or y equal to something like 1.5125 or even, 1.0 (In C++ 1.0 is different from 1) would cause an error during compilation. So how do we remedy this? By converting the values before we pass them of course. Converting data from one type to another is called "type casting". C++ provides many different methods of typecasting, two equivalent forms of which are outlined below.

float fPoint = 5.7; // Declare a variable to cast
int iVar = (int)fPoint; // C-Style type cast, iVar = 5
int iVar = int(fPoint); // Functional  notation, iVar = 5

Not all values can be typecast. For instance, a string cannot (for obvious reasons) be typecast to an integer. Also, as seen above a floating point number is truncated when cast to an int (or any non-floating point type).

[edit] Constants

C++ also provides a mechanism for defining named constants. In GML, constants are defined only in extensions or through the IDE, however, in C++ you must define them in the code yourself. The definition of a constant looks exactly like that of a variable except that the type is preceded by the "const" keyword. For instance:

const char cRose = 'I';

As you would expect, "cRose" is now set to the character 'I' for the duration of its lifetime.

[edit] Arrays

Unlike GML in which arrays expand automatically as you use higher indices, C++ arrays are of a fixed size which you define upon their creation. The creation of a 1-dimensional array with 5 elements looks like this:

char cArray[5];

This creates an array called cArray with a length of 5 (remember, the first index in an array is 0 in C++ and GML, so cArray[4] is the highest index one can reference). Just like with variables, arrays can be initialized with values. The following initializations are equivalent (note that the use of a string literal as the initialization value is only valid for an array of characters):

char cRose[] = "Is Back!";
char cRose[] = {'I','s',' ','B','a','c','k','!','\0'};
char cRose[9] = {'I','s',' ','B','a','c','k','!','\0'};

It is also possible to only partially initialize an array. If an array has a set length, but you only initialize it with part of that length, the rest of the array is initialized as 0 or, in the case of a char, a null value ('\0'). So for instance, all the values in the following array have been initialized to 0.0:

float fArray[10] = {0.0};

Arrays in C++ may also be multi-dimensional. Unlike GML however, C++ supports an arbitrary number of dimensions, so to make a 10x12 two dimensional table one might write:

int iSquareGrid[10][12];

Or to make a four dimensional 5x5x5x5 grid one might write:

int iTesseractGrid[5][5][5][5];

Accessing multi-dimensional arrays works the same as regular arrays, except that you must provide an index for each dimension.

[edit] Strings

Remember when I said there was no fundamental data type that represented a string? This is because strings can simply be thought of as arrays of characters. At its most basic form a string consists of an array with length string_length + 1. The extra character on the end is called a terminator and in C++ is represented by the null value (shown above in the form of the escape sequence "\0"). The terminator is not visible when you output the string to the screen, and is automatically added when you initialize a string from a string literal. However, it is important to remember that it is there. Its only purpose is to tell the program where the end of the string is in memory so that your program does not keep seeking and read other memory which could cause problems.

[edit] Recap of Variables

As you can see, variables, constants, and strings in C++ are quite different than those the typical GML user is acquainted with. Not only do variables and constants require initialization with a type, but they can not change that type once they have been created. Also, values in C++ must be typecast before they can be assigned to variables of a different type. We also saw that arrays in C++ can be n-dimensional (as opposed to the 2D cap imposed by GML). However, variables are fairly useless on their own; to make a useful program one needs to perform operations and actions on those variables. For this, we need to explore operators and functions.

[edit] Operators

C++ operators work in much the same way as GML operators work, with a few exceptions. First of all, you must remember not to mix and match types when performing operations in C++. If we add an int to a float, our compiler will most likely perform implicit typecasting (doing it in the background, without our knowledge to prevent an error) which is sometimes good, however, if you try to add a float to an int you may truncate the fractal part of the number by mistake! So if you need to mix and match types be sure to perform the appropriate type-cast. Secondly, there are more operators in C++ and not all of the ones that exist in GML work the same way. It is important to understand these changes or the resulting errors may cause you a lot of heartache. For instance, in GML the '=' operator can function as both an assignment operator and a comparison operator. In C++, however, it is always an assignment operator and '==' functions as a comparison operator. Here is a non-exhaustive list of basic C++ operators:

Operator Name

Syntax

Exists in GML

Arithmetic Operators

Unary Plus

+a

No

Addition (Sum)

a + b

Yes

Prefix Increment

++a

No

Postfix Increment

a++

No

Unary Minus (Negation)

-a

Yes

Subtraction (Difference)

a - b

Yes

Prefix Decrement

--a

No

Postfix Decrement

a--

No

Multiplication (Product)

a * b

Yes

Division (Quotient)

a / b

Yes

Modulus

a % b

Yes (mod)

Assignment Operators

Assignment

a = b

Yes

Assignment by Addition

a += b

Yes

Assignment by Subtraction

a -= b

Yes

Assignment by Multiplication

a *= b

Yes

Assignment by Division

a /= b

Yes

Assignment by Modulus

a %= b

No

Assignment by Bitwise Left Shift

a <<= b

No

Assignment by Bitwise Right Shift

a >>= b

No

Assignment by Bitwise AND

a &= b

Yes

Assignment by Bitwise OR

a |= b

Yes

Assignment by Bitwise XOR

a ^= b

Yes

Comparison Operators

Less Than

a < b

Yes

Less Than or Equal To

a <= b

Yes

Greater Than

a > b

Yes

Greater Than or Equal To

a >= b

Yes

Not Equal To

!=

Yes

Equal To

==

Yes

Logical Negation

!a

Yes

Logical AND

&&

Yes

Logical OR

||

Yes

Bitwise Operators

Bitwise Left Shift

a << b

Yes

Bitwise Right Shift

a >> b

Yes

Bitwise One's Compliment

~a

Yes

Bitwise AND

&

Yes

Bitwise OR

|

Yes

Bitwise XOR

Yes

Other Operators

Function Call

a()

Yes

Subscript

a[]

Yes

Indirection (Dereference)

*a

No

Address of (Reference)

&a

No

Member by Pointer

a->b

No

Member

a.b

Yes

Cast

(type)a

No

Comma

a , b

No

Ternary Conditional

a ? b : c

No

Scope Resolution

a::b

No

Size-of

sizeof(a)
sizeof(type)

No

Type Identification

typeid(a)
typeid(type)

No

Allocate Storage

new type

No

Allocate Storage (Array)

new type[n]

No

Deallocate Storage

delete a;

No

Deallocate Storage (Array)

delete[] a;

No

Exception

throw;
throw a;
throw type;

No

As you can see, C++ provides a much wider range of operations than does GML. Most of these operators you have seen before, however, a few are not implemented at all in GML either because GML does not need them, or because there are other methods for achieving the same thing. For instance, the auto-increment operators (a++ and ++a) are not used in GML (These operators simply add 1 to the number, the difference in them is in how it is returned, but we won't cover that here).

[edit] Functions

Both the sample programs discussed in previously contain one thing in common, the function: main(). “main” is the main (surprise!), function in any C++ program; it is where execution of your code starts. Here “main” is being declared a little differently than you may be used to seeing it. In GML, all functions are either built in, or must be defined through extensions or scripts (which are called as if they are functions), however, their name is defined in the IDE or with the #define macro if they are being imported from a file. In C++ however, functions are defined in the code. The basic layout of a function is this:

type function(type argument_name, type second_argument_name, ...)
{
 [statement];
 [statement];
 ...
}

You may notice a few differences between GML and C++ here. First of all, you might notice that the function has a list of its arguments in its definition. This is because arguments to C++ functions can have any name you want, not just argument0, argument1, argument2, etc. and because C++ functions do not have a set number of arguments (GML's built-in functions have a set number of arguments and user-defined functions always have 16 arguments). Also, you can see that (as we discussed previously) the function and all of its arguments have a type. The type of a function is called its "return-type" and is, as we stated earlier, the type of value the function is expected to return. Functions can also have a special type called "void" which means that they return nothing at all (so the return statement would be “return;” which would throw an error in GML). In any C++ program the “main” function (where execution starts) is required to have an “int” return type (not all compilers comply with the standard in this area, but you should). So let’s look at a basic function:

void fnDisplay(char* text)
{
 cout << text;
 return;
}

This function simply takes a string as an input (we'll talk about what the asterisk does later) and outputs it to the screen. This function is dependent on your including the iostream file in your project. This can be achieved by adding the line “#include <iostream>” somewhere above the function.

[edit] Encapsulation in Object Oriented Programming

GML is, at heart, an object oriented language. However, while it does have objects and support single inheritance (subtyping doesn't really apply), it is fundamentally flawed if we look at it from an OOP perspective. Object oriented programming is about much more than just having objects; it is a methodology in which each distinct feature of a program is separated from and hides its inner workings from every other feature, containing its own private member functions and variables enclosed by a set of methods which allow it to interact with the outside world. These principles, separation of concerns and information hiding, make up the object oriented design principle known as encapsulation. To illustrate why encapsulation is important let us consider a real world example: an ATM. If we were to design an ATM but we left off the outer shell and the buttons then anyone could walk up and take as much money out of the ATM as they wanted. This would clearly not be a very desirable system for the bank that owns the ATM. Or, lets say you were an honest person and wanted to take out only a little money and have that amount subtracted from your checking account. Our ATM, however, does not have any buttons, so you would need to reach into the ATM, figure out how it works, maybe wire in a laptop, and communicate your request to it yourself. This is not a very desirable situation for you, the user. All things considered, maybe its a good idea the ATM is how it is. This is the central idea behind the concept of encapsulation and is core to the OOP philosophy.

[edit] Classes and Objects

Earlier, we talked about how GML normally has its resources (including objects) defined in the IDE while C++ itself is not integrated so heavily with an IDE and defines its resources in code. We then looked at a simple example which defined an object in C++, however, we didn't really learn what each part of that code meant. Now we are going to learn exactly how objects in GML and C++ differ and how to define an object in C++. First of all, we need to clear up some confusing terminology. What is called an object in GML is called a class in most other object oriented programming languages (such as C++) and what is called an "instance of an object" in GML is called an object in C++. So in C++ each object is an instance of a class. "Class", incidentally, is the specifier used when creating a new class in code, like so:

class cFlashLight<
{
 // Class members here
};

The code above creates a new class called "cFlashLight", however, it is rather empty and doesn't do much. A real world flashlight has a few properties such as on and off, or the battery level. So lets add some properties:

class cFlashLight
{
 public: 
 bool on;
 char batteryLevel;
};

Simple enough, we just declared two variables called "on" and "batteryLevel". But now we have another funny keyword, "public." This is called an access specifier and is central to C++'s implementation of the concept of encapsulation. The access specifier is like the outer shell of our ATM from the previous section. It defines who is allowed to access the resources which fall under it. C++ defines three access specifiers for you to use (if you do not define an access specifier, everything is defaulted to private):

These three specifiers work like the keys to your house, you normally want your house private, but you may give your friends a key so that they can prevent your pets from starving to death when you go to the beach for a week. If you have kids, your house may just be "protected"... your friends have a key, but so do your kids so that they don't have to sit outside by the bus stop when they get home from school in the evenings until you get home from work. And finally, if you leave your door open and let anyone come in or out and take anything they want your house is public. But what sort of friends does an object have? Turns out, you get to decide. You can declare any function a friend of a class (meaning it gets to ignore the private and protected and go strait to the inner workings of your class). You can also declare a class a friend of another class. This is done by simply declaring the function (or class) with the "friend" keyword inside the class. Lets see an example:

int friendlyFunction();

class cFlashLight { private: bool on; char batteryLevel;
public: friend int friendlyFunction(); };

Friend functions and classes are a useful tool, however, declaring to many functions as "friends" defeats the purpose of the object oriented methodology, so use sparingly. In GML, of course, all functions are global and all "local" variables are public unless they are localized to a single script using the var keyword; so in GML essentially everything is a "friend" of everything else.

[edit] Constructors

Of course, when we create a class we normally want to have each member of the class (in this case our variables “on” and “batteryLevel”) contain some default value. This is akin to setting the value of variables in the Create Event in Game Maker except that Game Maker does not require you to define your variables first. To use our “create” event, we will create a public function called a constructor. The constructor has a few interesting properties, namely, it shares a name with the class itself (cFlashLight), it has no return type (not even void), and it is always public. The constructor is automatically invoked by the program when we create our class (more on that later) and looks something like this:

class cFlashLight
{
 private:
  bool on;
  char batteryLevel;
public: cFlashLight() { on = false; batteryLevel = 255; } };

This constructor defaults the flashlight's status to off and full battery. We can also pass the constructor arguments, as seen in the following example:

class cFlashLight
{ 
 private:
  bool on;
  char batteryLevel;
public: cFlashLight(bool isOn, char initialBatteryLevel) { on = isOn; batteryLevel = initialBatteryLevel; } };

To call this constructor we would simply provide arguments when creating an object, for instance:

cFlashLight objFlashLight(true, 128);

[edit] Destructors

Of course, if we have a create event, we also probably want a destroy event (note that these are not really events; C++ events are a totally different concept which we will not discuss in this article, the constructor and destructor are simply analogous to the GML Create and Destroy events). The destructor (as it is called) works the same way as the constructor except that the constructor can take arguments whereas the destructor can not. The destructor's name is always the name of the class prefixed with a tilde (“~”) and has no return type. The destructor is used to perform cleanup of the function by say, freeing memory which has been dynamically loaded, deleting data structures used by the class, modifying counting variables, etc. The class above does not need a destructor (it has no dynamic memory allocated nor does it have any action to perform before it goes out of scope) to clean up after it, and to understand why destructors are necessary we really need to understand pointers, so we will talk a little more about destructors later.

[edit] The Scope Operator

Now that you know how to create a class, complete with member functions and variables, it is time to learn about the scope operator. The scope operator is simply a way of referencing members of a class from outside of that class and looks like this: Type::Member where type is the class and member is the variable or function you want to reference. Using this operator, it is possible to define our constructor for cFlashLight outside of the class:

class cFlashLight
{
 private:
  bool on;
  char batteryLevel;
 public:
  cFlashLight();
}; cFlashLight::cFlashLight()
{
 on = false;
 batteryLevel = 255;
}

In this case, use of the scope operator simply makes our code easier to read (this is especially useful when you are defining a lot of functions inside the class), however, it has its practical benefits too, especially when referencing static members.

[edit] Static Members

Static member functions and variables are one of C++'s most wonderful features. Variables may be defined as static in both functions and classes and functions may be defined as static when they are a member of a class. When a variable is declared as static inside a function, its value is retained even after the variable goes out of scope. This means that the next time you call that same function; the static variable has the same value. When you define a variable static within a class, every instance of that class will have the same value for that variable. And finally, when you declare a function static in a class you can call that function from anywhere without referencing a particular instance of that class (the function works on the entire class much like a static member variable). Let’s see an example, say we wanted to keep track of how many flashlight objects had been created above, we might try something like this:

class cFlashLight
{
 private:
  bool on;
  char batteryLevel;
  static int num;
 public:
  cFlashLight();
  ~cFlashLight();
  static int getNum();
};
cFlashLight::cFlashLight() // The constructor { on = false; batteryLevel = 255; num++; // Increment “num” by 1 } cFlashLight::~cFlashLight() // The destructor { num--; // Decrement “num” by 1 } cFlashLight::getNum() { return ( num ); }

Because "num" is static, its value is the same for all instances of cFlashLight. So in the constructor (and added destructor) we can simply add and subtract to and from num and its value will contain the number of instances of the class (you could do the same using a global variables and the create and destroy events in GML). Also, because our new, aptly-named, member function getNum is static, it can be referenced from outside the code with the scope operator ("::") without referencing any particular instance of the class.

[edit] Classes Recap

You now know how to create classes and add methods and members to them. This is perhaps one of the first areas of C++ that you have experienced which varies greatly from GML; however, its not over yet. In the next post we will learn about how to create instances of our classes and a little about "pointers" and how to use them to reference our classes.

[edit] Scope

Earlier we talked a tiny bit about scope, but it is now time we addressed it formally. GML users should be no strangers to the concept of scope. Every time you address a local variable from another object or instance using either the “with” statement or the “.” operator you are invoking the concept of scope. Normally when a variable (or function, or class, etc.) is “in-scope” memory is allocated for its storage and when it goes out of scope the memory which had previously been used to store it is released. For this reason it is often not desirable to use global variables in GML as they never go out of scope and will consume memory even when not in use. Besides global variables in GML there are also two types of local variables; regular local variables (which come into scope when they are declared for the instance in which they are declared and go out of scope when the instance is destroyed) and variables declared with the “var” keyword which go out of scope at the end of the block of code in which they are declared. In C++, scope works a bit differently. First of all, variables and functions both have scope (In GML functions and scripts are always global); they can be global, local to a class or namespace, or local to a function (In C++ functions cannot be local to other functions; however, in some languages such as JavaScript this is allowed). To access something in a particular scope (and to differentiate it from something with the same name in another scope) we use the scope operator, or “::” (“.” in GML) or the “.” operator (which accesses members of an instance). For example, to access a static member called “numPoints” of a class “cPoint” we might use “cPoint::numPoints” or to access the value of the non-static variable “x” in the object “pPoint” we might write “pPoint.x”. There are also several other ways we might use “::” and “.”, one of which is to access members of a namespace.

[edit] Namespaces

In C++ it is possible to create your own sub-scope's called namespaces. Namespaces are simply a way of grouping variables, functions, and classes under a given name and can be created using the “namespace” keyword as in the following example:

namespace nsOne
{
// Namespace members here
int xx, yy;
}
namespace nsTwo
{
 int xx, yy;
}

This declares two namespaces, one called nsOne and the other nsTwo. Each of these namespaces has a variable called xx and another called yy. This means that there are two versions of xx and yy in the same scope (lets just assume the namespaces are both global); they just have different sub-scopes. To access something that is in the nsOne namespace (say, the variable 'xx') we would use the scope operator as described above: “nsOne::xx”. If you want to take a shortcut and skip writing out the full name of the variable you may call a using statement which will bring the contents of the namespace “in-scope”:

#include <iostream>
using namespace std;
int main()
{
 cout << "Hello World!\n";
 return ( 0 );
}

is equivalent to:

#include <iostream>
int main()
{
 std::cout << "Hello World!\n";
 return ( 0 );
}

Because “cout” is in the “std” namespace (which is defined within the included "iostream" file).

[edit] Types and Classes

Now it is time to discuss creating instances of our classes. But first, we must know a bit about the distinction between types and classes: there isn't one. As it turns out, in C++ classes are simply user defined types which are just as valid (in the eyes of a C++ compiler) as the built in types. Therefore the type “cUserClass” is just as valid as the type “int”. This means that to declare a new instance of a class, we simply declare a variable with its type as the name of the class. For instance, the following code is valid:

namespace subScope
{
 // A class called cString
 class cString
 {
  public:
  static char* tString;
 }
}
using namespace subScope; cString xx; int initialize(char* initString) { xx.tString = initString; return ( 0 ); }

Of course, this code contains several awful coding practices, however, for the sake of simplicity, we'll leave it as is.

[edit] Pointers

Before we can really say we have a good grasp on C++ we must learn about one of C++'s most important features: pointers. In GML when one wants to access resources in memory (say, a sprite) one must use functions such as sprite_replace(). In C++ however, one does not need to make any function calls, one can simply access the memory allocated for said resource dynamically. Now I must warn you, everything we've seen up to this point has been fairly easy to translate to GML, however, pointers have no equivalent in GML (though, as I stated before, they can be emulated to some degree with various resource functions). Pointers often muddy the waters of C++, and those that go in, if they don't go in properly, don't always come out. With that said, they are a powerful force which, when used properly, can provide tremendous speed boots to your software and enable you to do things that are difficult if not impossible in languages that do not provide any sort of direct memory access. But what are pointers? Pointers are a special type of variable, however, instead of storing a number or some other piece of data, they store the location in memory of that data. They “point to” the physical location of a piece of data in your computers memory. Why would one want to do this though? Lets look at an example. Imagine that we have an object (that is, an instance of a class) which we need to pass to a function for modification. If we simply create an argument with the type of our class, we are actually passing the function a copy of our object, however, what if we only want to modify one little integer in the object and don't need to waste memory on a full copy (which will then be copied back over the original anyhow)? Instead, we can just pass the address of our object (using a pointer) and let the function modify the original object instead of a copy. Besides that there are many other reasons to use pointers, including dynamic memory access and shared data.

[edit] The Reference Operator

Each variable is stored somewhere inside the computer’s memory and each chunk of memory is given a specific address. Sometimes it is useful to know that address and for this we use the reference operator & (ampersand). For instance, if we wanted to get the address of the integer “iVar” we might write “&iVar” which means “return the memory address of iVar”. To create a pointer (a variable which “points to” the location at which some data is stored) we might simply write “iPointer = &iVar”. To illustrate what this has done, lets look at the following code:

iVar = 2; // Assume iVar is stored at address 1366
iVarCopy = iVar;
iPointer = &iVar;

At the end of this code, the three variables we have declared have the following values: iVar is equal to 2, iVarCopy is also equal to 2, and iPointer is equal to 1366 (which is an arbitrary number that I made up for the purpose of illustration) which is the location of iVar in memory. Note that if we set iPointer equal to &iVarCopy it would not be the same as setting it equal to &iVar as iVarCopy and iVar merely share the same value; they are two completely separate variables after the copy is completed and reside at different addresses in memory.

[edit] The Dereference Operator

The real pointer magic only comes in, however, when we use the reference operator in conjunction with something called the dereference operator. The dereference operator is symbolized by the “*” (asterisk) symbol and is read “value pointed at by”. For instance, in the above example, iPointer is equal to 1366, but this is just a number that references a memory location, if we set iPointer equal to, say 5, we are not setting the memory at which iVar is stored to 5, we are just changing iPointer to equal 5 (it is no longer pointing to anything). If we wanted to get the value pointed to by iPointer we would write “*iPointer” which can be read “the value pointed to by iPointer” which in this case is 2. So if we said “newVar = iPointer” newVar would equal 1366, but if we say “newVar = *iPointer” newVar would equal 2.

[edit] Declaring Pointers

Because pointers must point to a certain data type (int, char, etc.) one must declare variables with pointer types to have the same type as the variable that it points to. To specify that a variable is a pointer, we use the splat operator (*) just like we do when dereferencing pointers, however, it is not the dereference operator, it is simply the same symbol being re-used in a confusing manner (just like it is also used for multiplication). For instance, to declare iPointer which was used above, we would write: “int *iPointer”. If we want to declare multiple pointers, we must have a splat-operator for each variable, for instance: “”int *iPointer1, *iPointer2”.

[edit] Pointers and Arrays

Pointers and arrays are very closely related topics. Internally, the array is merely a pointer to the first node in that array, for instance if we have an array of chars “char abc[] = {'a', 'b', 'c'};” then “abc” is a pointer to the memory location at which 'a' (the first char) is stored. When we use the [] operator on abc (lets say, abc[1] which is 'b') we are simply writing *(abc+1) in short hand. This means that arrays are simply const pointers and the [] operator is simply a special form of dereference operator. This means that to make a string, we don't have to use a limited char array, we can use the char* data type. For instance, 'char *sVar = “This is a string!”;' is valid code. There are many other strange forms pointers can take including pointers to pointers, void pointers (which allow you to pass arbitrary data types, but cannot be dereferenced), null pointers, etc. However, they will not be discussed here.

[edit] Dynamic Memory

Before now, we declared everything with a given amount of memory. Ints, chars, and floats all had a single, static amount of space which was available to them. However, when storing data and creating data structures it is often beneficial to have dynamic memory which can be allocated and deallocated at will. For dynamic memory, C++ uses a block of memory called the “heap” and, to allocate memory off the heap, the new and new[] (for arrays) keywords are used. for instance, if we have an integer pointer called “int *iPointe” and we want to point to a new chunk of memory off the heap, the appropriate code is “iPointer = new int;” This can be read as “allocate a new chunk of memory off the heap of size sizeof(int) and set iPointer to point to it.” To delete memory off the heap, we use the aptly named “delete” keyword (and “delete[]” for arrays). Using this method we can allocate memory for instances of our classes dynamically and can delete large objects as soon as we no longer need them. Remember the constructor and destrucctor we talked about before? We stated that the constructor was called automatically whenever the class was created (this includes dynamically using the “new” keyword), but we never really talked about the destructor. As it so happens, the destructor is called whenever an instance of the class is deleted or goes out of scope. This means that the destructor is called whenever an object is deleted. For example, take a look at the following code:

class cFlashLight
{
 private:
  bool on;
  char batteryLevel;
  int count;
public: cFlashLight(bool isOn, char initialBatteryLevel) { on = isOn; batteryLevel = initialBatteryLevel; count++; }
cFlashLight() /* For this class we have two constructors, this is called “overloading” but we will not discuss it further here. Let it suffice to say that when no arguments are given, this constructor will be called and when a bool and a char are given the above constructor will be called. */
{ on = true; batterlevel = 255; count ++; }
~cFlashLight() { count --; }
};
int main() { cFlashLight *a = new cFlashLight(); // count = 1 delete a; // count = 0 return ( 0 ); }

The destructor should also be used to cleanup any memory which is allocated using “new” in the instance. This is called garbage collection, and C++ will not do it for you. If you forget to call delete, then the memory allocated for your resources will not be freed and your program will no longer be able to use it.

[edit] Conclusion

Computers are extremely complex machines and to make interfacing with them easier they are built in layers, following the fundamental concept of encapsulation. Higher level programming languages such as GML operate quite differently from lower-layer languages such as C++ and understanding the fundamental concepts of the lower level language (the stuff going on behind the scenes in the higher level languages) can make you a better, more “well rounded”, programmer. High and low level languages both have their ups and downs, and only by understanding the differences between them can you chose the right language for the task at hand.

[edit] See Also

[edit] External links

Personal tools