C++11 features in Magnum: Better memory management

Re­gard­less to what lan­guage you use, you al­ways need to think about mem­o­ry man­age­ment. Garbage col­lec­tors might give you a sense that it’s done au­tomag­i­cal­ly, but to get more per­for­mance you need to have con­trol over heap us­age in code run­ning at 60 FPS. C++11 helps to avoid many heap al­lo­ca­tions with­out sac­ri­fic­ing us­abil­i­ty.

Get­ting rid of ex­plic­it mem­o­ry man­age­ment

Many ob­jects in the en­gine are mem­o­ry-heavy (e.g. ver­tex da­ta), which means that copy­ing them is not a good idea. More­over, OpenGL ob­jects (such as tex­tures, mesh­es or buf­fers) aren’t copy­able, sim­i­lar­ly to e.g. the std::fstream ob­jects. In C++03 world it meant that you had ba­si­cal­ly two so­lu­tions for avoid­ing copy­ing. You can force the us­er to cre­ate an in­stance be­fore­hand and then use the func­tion to per­form some ac­tion on it:

Trade::MeshData3D mesh;
importer->meshData3D("steam-locomotive", mesh);

Or you can cre­ate the in­stance on heap, re­turn point­er to it and in­struct the us­er to ex­plic­it­ly delete the ob­ject af­ter­wards:

Trade::MeshData3D* mesh = importer->meshData3D("steam-locomotive");
if(mesh) {
    // ...
delete mesh;

Both meth­ods re­quire some non-triv­ial ac­tion from the us­er. In the first case the ob­ject might have non-triv­ial con­struc­tor (which ac­tu­al­ly is the case with Trade::Mesh­Data3D) and the func­tion has no in­tu­itive way to tell that the im­port went bad and the mesh is now in­valid. The sec­ond case does heap al­lo­ca­tion, which is slow­er than stack al­lo­ca­tion, but more im­por­tant­ly re­quires ex­plic­it dele­tion, which more of­ten than not leads to ac­ci­den­tal mem­o­ry leaks.

C++11 in­tro­duces move se­man­tics, which means that the ob­ject can be just moved out from the func­tion with­out copy­ing:

Trade::MeshData3D mesh = importer->meshData3D("steam-locomotive");

While this is fi­nal­ly a clean one­lin­er, it now isn’t pos­si­ble to in­di­cate that the mesh im­port failed.

The long-await­ed std::optional, which got fi­nal­ly in­clud­ed in C++17, aims to solve ex­act­ly this. The op­tion­al ob­ject con­tains a bool­ean in­di­cat­ing the state and ad­di­tion­al space where the in­stance can be stored (i.e., with­out any heap al­lo­ca­tion). The class is in­spired with boost::optional but since the en­gine needs to be­have the same al­so on C++11 com­pil­ers, Cor­rade in­tro­duced its own Con­tain­ers::Op­tion­al that doesn’t re­quire C++17 and works on C++11-ca­pa­ble com­pil­ers as well.

Containers::Optional<Trade::MeshData3D> mesh = importer->meshData3D("steam-locomotive");
if(mesh) {
    // ...

Last­ly, some func­tions re­turn poly­mor­phic types, which can’t be done any oth­er way than with heap al­lo­ca­tion. C++11’s std::unique_ptr will han­dle the dele­tion im­plic­it­ly and un­like std::shared_ptr it adds on­ly a tiny over­head, be­cause it doesn’t need to do any ref­er­ence count­ing.

std::unique_ptr<Trade::AbstractMaterialData> material = importer->material("scratched-copper");
if(material) {
    // ...

Ini­tial­iz­er lists

In C++03 code, when you want to pass a list of some val­ues (known at com­pile-time) to a func­tion, the most per­for­mant way is this:

Source* sources[] = {backgroundMusic, boom, laughter, eternalPain};
Audio::Source::play(sources, 4);

If you want to write the same as one-lin­er, you can achieve that us­ing spe­cial­ly craft­ed con­tain­er con­tain­ing some mag­ic with operator, or operator<<, but with not ex­act­ly in­tu­itive us­age and at a cost of run-time heap al­lo­ca­tion, for ex­am­ple:

Audio::Source::play((Array<Source*>(), backgroundMusic, boom, laughter, eternalPain));

C++11’s std::ini­tial­iz­er_list al­lows to write this as a one-lin­er with­out any ad­di­tion­al over­head. In many cas­es Mag­num al­so pro­vides a Con­tain­ers::Ar­rayView over­load for lists of run-time de­pen­dent size.

Audio::Source::play({backgroundMusic, boom, laughter, eternalPain});

Vari­able-length ar­rays

While this fea­ture is of­ten frowned-up­on, it has its use. Many func­tions in OpenGL and oth­er frame­works, most no­tably the re­cent AR­B_­mul­ti­_bind ex­ten­sion, ac­cept ar­rays of in­te­gers to do an op­er­a­tion on a giv­en list of ob­jects. In pub­lic Mag­num API this is of­ten done us­ing std::ini­tial­iz­er_list of point­ers to giv­en ob­jects, as shown above. But in­ter­nal­ly the li­brary needs to ex­tract IDs from all ob­jects, put them in some new­ly al­lo­cat­ed ar­ray of vari­able length, pass that ar­ray to giv­en func­tion and then delete the ar­ray again. Stack-al­lo­cat­ed ar­rays solve this and the fea­ture is al­ready avail­able as non-stan­dard ex­ten­sion in GCC, but it’s not yet used in Mag­num due to pos­si­ble porta­bil­i­ty is­sues.

Set­ters and move se­man­tics

Set­ters in C++03 code com­mon­ly take con­st ref­er­ence to ob­ject and then copy it to the des­ti­na­tion:

void Configuration::setFilename(const std::string& filename) {
    _filename = filename;

While tak­ing ob­ject by ref­er­ence avoids cre­at­ing an­oth­er copy com­pared to tak­ing ob­ject by val­ue, it doesn’t avoid un­nec­es­sary copies al­to­geth­er:

Configuration conf;
std::string file = "game.conf";
conf.setFilename(file);         // okay, copied from named variable
conf.setFilename("game.conf");  // bad, copied from temporary variable

In the sec­ond case, tem­po­rary std::string vari­able is cre­at­ed (first al­lo­ca­tion), then its con­tents are copied (sec­ond al­lo­ca­tion) and then this tem­po­rary is dis­card­ed (deal­lo­ca­tion). The un­need­ed al­lo­ca­tion and deal­lo­ca­tion can be avoid­ed us­ing move se­man­tics, but from us­er point-of-view the us­age is still the same. In Mag­num all set­ters tak­ing heavy types (strings, vec­tors…) are done this way.

void Configuration::setFilename(std::string filename) {
    _filename = std::move(filename);

Se­mi-au­to­mat­ic mem­o­ry man­age­ment

Han­dling mem­o­ry deal­lo­ca­tions in in­ter-de­pen­dent scene graph with many shared re­sources is a pain to do man­u­al­ly and this is ex­act­ly the case where a sane garbage col­lec­tor is ac­tu­al­ly use­ful. Mag­num of­fers two ways of au­to­mat­ic mem­o­ry man­age­ment: the scene graph and re­source man­ag­er.

Scene graph is a tree of ob­jects, sim­i­lar to what Qt’s QObject hi­er­ar­chy is. When some ob­ject is de­stroyed, all its chil­dren and at­tached fea­tures are au­to­mat­i­cal­ly de­stroyed too. In fact, to­geth­er with method chain­ing you can add ob­jects to a scene and con­fig­ure them with­out even sav­ing them to a vari­able. You can read more about scene graph in the doc­u­men­ta­tion.

(new Chair(&scene))
    ->translate({0.4f, 0.0f, -1.0f})

Re­source man­ag­er of­fers more fine-grained op­tions. Each re­source stored there can be ei­ther stat­ic (delet­ed on man­ag­er de­struc­tion), man­u­al­ly man­aged (delet­ed on ex­plic­it free() call, which can be done ei­ther for par­tic­u­lar re­source type or for whole man­ag­er) or ref­er­ence-count­ed (delet­ed when last ref­er­ence to the ob­ject is re­moved). The be­hav­ior is more thoroguh­ly de­scribed in Re­source­M­an­ag­er class doc­u­men­ta­tion.