Lambda výrazy v C++0x a bloky v LLVM

2. 8. 2011 11:42 (aktualizováno) zboj

Pokud píšete kód v C++ a používáte lambda výrazy, resp. bloky, mohou se vám hodit makra, která vás odstíní od rozdílů v syntaxi C++0× (nový standard C++) a LLVM (implementace Applu). Zatímco lambda výraz „x → x + 1“ se v C++0× píše

[](int x){ return x + 1; }

implementace Applu vypadá takto:

^(int x) { return x + 1; }

Ve svém kódu používám makro preprocesoru, které mě od rozdílu v syntaxi odstíní, a zápis pak vypadá takto:

BLOCK()(int x) { return x + 1; }

Pokud potřebujete deklarovat proměnnou pro lambda výraz, resp. blok, lze také použít makro (následně uvádím zápis v C++0×, LLVM a s makrem):

function<int(int)> block int(^block)(int) BLOCKVAR(block, int, int)

První argument je jméno proměnné, druhý argument je typ návratové hodnoty a dále následují typy argumentů bloku.

Makro BLOCK dostává jako parametr seznam proměnných použitých v bloku (nutné pro C++0×, v LLVM se tento seznam ignoruje). Příklad použití:

int x = 1; BLOCK(x)(int n) { return x + n; }

Pokud bychom proměnnou x neuvedli, oznámil by kompilátor GCC chybu „‚x‘ is not captured“ (pokud použijete verzi GCC od Applu, budou se používat bloky Applu, jejichž syntax a implementace se od té v LLVM téměř neliší).

Zde je deklarace použítých maker:

#ifdef __BLOCKS__
#define BLOCKVAR(var, ret, …) ret (^var)(__VA_ARGS__)
#else
#define BLOCKVAR(var, ret, …) function<ret(__VA_ARGS__)> var
#endif
#ifdef __BLOCKS__
#define BLOCK(...) ^
#else
#define BLOCK(...) [__VA_ARGS__]
#endif

Vzhledem k rozdílné sémantice je třeba pamatovat na proměnné používané v bloku referencí. Ve výčtu pro C++0× je nutné použít znak &, např. &x. Pro LLVM se při deklaraci takové proměnné používá __block, např. __block int x. Tohoto rozdílu se lze zbavit makrem, které pro C++0× definuje __block jako nic (ignoruje se).

Další rozdíl spočívá ve faktu, že bloky v LLVM se vytváří na zásobníku (a jsou tedy např. po návratu z funkce zrušeny). Pokud potřebujete delší životnost bloku, musíte použít Block_copy, abyste zkopírovali blok na haldu (a až už blok nepotřebujete, tak Block_release). V C++0× se o to starat nemusíte, takže kvůli přenositelnosti musíte Block_copy a Block_release používat, ale pro C++0× si můžete tyto funkce nadefinovat tak, že první vrací svůj argument (předaný blok) a druhá nedělá nic.

Úlohy na pozadí

Bloky se hodí kromě jiného pro spouštění kódu na pozadí (v jiném vlákně). Bohužel ani zde neexistuje (zatím) žádná univerzálně použitelná metoda. S využitím výše uvedeného makra používám často ještě DETACHTHREAD:

DETACHTHREAD(BLOCK() { cout << "OK" << endl; });
Deklarace pro LLVM (s využitím frameworku Foundation v Cocoa pod OS X nebo iOS) a C++0× s využitím nové třídy STL thread lze makro definovat takto:
#ifdef __BLOCKS__
static NSOperationQueue* globalOperationQueue = [[NSOperationQueue alloc] init];
#define DETACHTHREAD(block) [globalOperationQueue addOperation: [NSBlockOperation blockOperationWithBlock:
block]]
#else
#define DETACHTHREAD(block) thread(block)
#endif

Kód není v obou prostředích zcela ekvivalentní, neboť zatímco thread prostě vytvoří a spustí nové vlákno, NSOperationQueue spravuje vlastní pool vláken a kód pouští podle vlastního uvážení (např. podle počtu procesorů, resp. jader). Bylo by možné použít nízkoúrovňovou třídu NSThread, ale v naprosté většině případů není tento rozdíl podstatný.

NB: Výše uvedený kód byl testován s GCC 4.5 a LLVM 1.7.

Sdílet