D Structs vs Classes and a Simple Template Example

posted in D Bits
Published August 02, 2011
Advertisement
D shares a lot of similarities with C++ and Java, but a lot of the sameness is just a bit different. One of the first places new users see this is in the handling of structs and classes.

D's classes have more in common with those of Java than C++. For starters, they're reference types. Whenever you need an instance of a class, you new it and it is allocated on the heap (though scoped stack allocation is possible via a standard library template). Structs, on the other hand, are value types. But, you can create pointers to them, allocate them on the heap with new, or pass them by reference to a function that takes ref parameters. Another difference between the two is that classes have a vtable and can be extended. Structs, however, have no vtable and can not be extended. That also applies to the Java-esque interfaces in D -- classes can implement them, structs cannot.

Because of the differences between the two, there are certainly some design implications that need to be considered before implementing an object. But I'll talk about that another day. For now, I just wanted to give a little background before getting to an example illustrating the primary motivation for this particular post.

While working on Dolce, I thought it would be a good idea to wrap Allegro's ALLEGRO_CONFIG objects, simply because working with a raw, string-heavy C API in D can be a bit tedious. You have to convert D strings to C strings and vice versa. In this particular case, you also have to convert from string values to integers, booleans, and so on, since Allegro only returns raw C strings.

So what I wanted was something that allowed me to create and load configs, then set and fetch values of the standard built-in types. Rather than implementing a separate get/set method for each type, I chose to use templates. And in this case, I didn't want the whole object to be templated, just the methods that get and set values.

Initially, I implemented it as a class, but in the rewrite of Dolce I pulled the load,create and unload methods out and made them free functions. I also realized that this is a perfect candidate for a struct. The reason is that it contains only one member, a pointer to an ALLEGRO_CONFIG. This means I can pass it around by value without care, as it's only the size of a pointer. Here's the implementation:


struct Config
{
private
{
ALLEGRO_CONFIG* _config;
}

ALLEGRO_CONFIG* allegroConfig() @property
{
return _config;
}

bool loaded() @property
{
return (_config !is null);
}

T get(T)(string section, string key, T defval)
{
if(!loaded)
return defval;

auto str = al_get_config_value(_config, toStringz(section), toStringz(key));
if(str is null)
return defval;

// std.conv doesn't seem to want to convert char* values to numeric values.
string s = to!string(str);
static if( is(T == string) )
return s;
else
return to!T(s);
}

void set(T)(string section, string key, T val)
{
if(!loaded)
return;

static if( is(T == string) )
auto str = val;
else
auto str = to!string(val);

al_set_config_value(_config, toStringz(section), toStringz(key), toStringz(str));
}
}


You'll notice that the _config field is private. I don't normally make struct fields private, as the structs I implement are usually intended to be manipulated directly. But in this case I thought it prudent to hide the pointer away. I still provide access through the allegroConfig property (and I'll discuss properties another day) in case it's really needed.

So you may also be wondering how _config is ever set if it's private and there's nothing in the struct itself that sets the field. The answer lies here:


Config createConfig()
{
return Config(al_create_config());
}


This is something that frequently trips up D newbies coming from other languages. What's going on is that the create function and the Config implementation are in the same module. You can think of modules as another level of encapsulation. And, for those who are steeped in C++ vernacular, you can think of modules as friends of every class and struct implemented within them. In other words, private class/struct members/methods are all visible within the same module. If you ever get to know D at all, you'll likely find this to be a very convenient feature.

Another thing that might jump out at you is the static if. This is one way to conditionally generate code at compile time. You'll frequently see it used in templates, though its use is not restricted to templates. Here, I'm testing if the type T is a string or not. In the get method, the value returned from Allegro is a char*, so it is converted to a D string. If the type of T is string, then there's no need for any further conversion and the D string can be returned. If not, the D string must be converted to the appropriate type (int, bool, long or whatever). Similarly, in the set method, a char* needs to be passed to Allegro, so any nonstring values are first converted to a D string. But if T is string, that step isn't necessary.

And now to the templates. D's template syntax is clean and extremely powerful. This example demonstrates the cleanliness part at least.


T get(T)(string section, string key, T defval)


If you've ever used C++ templates, it should be clear what is going on here. The type to be accepted is declared in the first pair of parentheses, the parameter list in the second pair. And in this case, a value of the specified type is returned.

Now, to use it:


auto config = createConfig();
auto i = config.get!int("Video", "Width", 800);
auto b = config.get!bool("Video", "Fullscreen", false);


Here, I've used the auto keyword for each variable. The compiler will infer the Config, int, and bool types for me. As for the template instantiations, notice the exclamation point used between the method name and the type. That's what you use to instantiate a template. Technically, I should be wrapping the type of the template in parentheses, like this:


auto i = config.get!(int)("Video", "Width", 800);


If you have a template with more than one type in the type list, then you have to use the parens. But if there is only one type, the compiler lets you get away with dropping them. For singly-typed templates, that has become a standard idiom in D.

If you decide to give D a spin and come from a C++ or Java background, I hope this post helps keep you from any initial confusion that might arise when you find that things aren't quite the same as you're used to.

You can learn more about D's structs, classes and templates at d-programming-language.org.
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement