Něco málo z C++ #1

Zdroj: SOOM.cz [ISSN 1804-7270]
Autor: independent
Datum: 13.3.2013
Hodnocení/Hlasovalo: 2/1

V diskuzi jsem zde nahodil několik hádanek z C++, na které pravděpodobně většina začátečníků nebude znát odpověď. Aby vysvětlení nezapadlo kdesi v hlubinách diskuze, vkládám to jako user text.

Předávání struktury parametrem

První hádanka spočívala v tom, co by mohlo být špatně na následujícím kousku kódu:

typedef struct NejakaStruktura { .. };
void nejakaFunkce(NejakaStruktura nazev) { ... }

NejakaStruktura a;
nejakaFunkce(a);

Tento kód půjde zkompilovat a bude dokonce i správně fungovat. Problém nicméně spočívá v tom, že ve většině případů by byl naprosto zbytečně pomalý kvůli tomu, že předávaná struktura se bude pokaždé kopírovat. V případě velmi malých struktur (nebo tříd) by to bylo pravděpodobně jedno, ale pokud bychom se bavili například o stovkách bajtů, zpomalení by mohlo být poměrně markantní.

Na níže uvedeném příkladu je vidět, jak to udělat pomocí tzv. referencí. V mém případě byl (při konstantním zatížení systému) výsledek 42s pro foo1 a 35s pro foo2. Samozřejmě podobná měření jsou velmi ošemetná záležitost, protože záleží na přesné konfiguraci kompilátoru a zvolené úrovni optimalizací, ale nějakou vypovídající hodnotu přesto má.

#include 
#include 
#include 

typedef struct TStruct
{
	int values[100];
};

int foo1(TStruct someStruct)
{
	int t = 0;
	for (int i = 0; i < 100; i++)
		t += someStruct.values[i];

	return t;
}

int foo2(TStruct &someStruct)
{
	int t = 0;
	for (int i = 0; i < 100; i++)
		t += someStruct.values[i];

	return t;
}

int main(int argc, char *argv[])
{
	TStruct myStruct;
	for (int i = 0; i < 100; i++)
		myStruct.values[i] = i;

	int t0 = time(NULL);
	for (int i = 0; i < 50000000; i++) 
		foo1(myStruct);

	int t1 = time(NULL);
	for (int i = 0; i < 50000000; i++) 
		foo2(myStruct);

	int t2 = time(NULL);

	printf(\"Foo1: %i\\n\", t1 - t0);
	printf(\"Foo2: %i\\n\", t2 - t1);

	return EXIT_SUCCESS;
}

Inicializace atributů

Druhá hádanka byla, jak inicializovat hodnotu const atributu třídy. V C++, narozdíl například od Javy, nelze hodnotu přiřadit rovnou při deklaraci atributu na témže řádku. Pokud by atribut nebyl označen modifikátorem const, bylo by možné jej inicializovat v libovolné metodě, nejčastěji pravděpodobně v konstruktoru. Nebudu vás již napínat, správná syntaxe vypadá takto:

class Foo
{
	public:
		Foo() :
			m_Value(0)
		{
			//
		}

	private:
		const int m_Value;
};

Tímto způsobem lze samozřejmě inicializovat všechny atributy, nikoliv pouze konstanty.

Výjimky

Poslední hádanka se týkala rozdílů při odchytávání výjimek vyvržených prvním a druhým způsobem.

//#1:
throw new Exception;

//#2:
Exception exception;
throw exception;

Rozdíl je v tomto případě naprosto zásadní. V prvním případě by byl vyvržen ukazatel. Abyste tedy výjimku mohli odchytit, museli byste rovněž v catch konstrukci uvést, že se jedná o ukazatel. Pokud by programátor nějaké knihovny použil právě tento (špatný) způsob a uživatel knihovny výjimku odchytával s předpokladem, že byla vyvržena pomocí druhého způsobu, program by mu při vyvržení výjimky spadl - vůbec by se neodchytla. V catch konstrukci by totiž byl uveden zcela jiný typ, než jaký výjimka ve skutečnosti měla. Další problém spočívá v tom, že v prvním případě by programátor musel myslet na to, aby po odchycení výjimky provedl její dealokaci pomocí operátoru delete.

Zajímavostí oproti jiným jazycím je to, že výjimky v C++ nemají žádný předepsaný typ a dokonce i konstrukce jako je tato bude validní.

int i = 5;
throw i;
Chraň vás mozek toto někde použít!

Můžete si prohlédnout ještě zdrojový kód, který demonstruje rozdíly při odchytávání výjimek jedním či druhým způsobem.

#include 

class Exception{};

int main(int argc, char *argv[])
{
	try
	{
		throw new Exception;
	}
	catch (Exception *exception)
	{
		puts(\"Odchyceno #1\");
		// exception->getErrorMessage()
		delete exception;
	}

	try
	{
		Exception exception;
		throw exception;
	}
	catch (Exception exception)
	{
		puts(\"Odchyceno #2\");
		// exception.getErrorMessage()
	}
}

Závěr

Tento článek si nekladl za cíl šířit žádná zásadní moudra, ale naznačit (poměrně banální) problémy, které vás v C++ mohou potkat. Domnívám se, že pro řadu začátečníků jsou tyto skutečnosti poměrně skryté a toto by pro ně tedy mělo být jen jakési základní nakopnutí. Zároveň mi přislo hloupé nahodit v diskuzi nějakou problematiku a nechat ji zcela bez odpovědi.

Ačkoliv článek nese v titulku "#1", neznamená to, že na něj budou navazovat další - ačkoliv vyloučené to není. Pokud by o to byl zájem, zvážil bych napsání něčeho dalšího.

PS: Tento článek píšu ve spěchu a navíc se za žádného velkého odborníka na C++ rozhodně nepovažuji, proto je možné, že v něm budou faktické či jiné chyby. Pokud si něčeho takového všimnete, nestyďte se upozornit mě na to v komentářích, rád chybu opravím.

Aktualizace: Díky upozornění Bystroushaaka byl článek upraven tak, aby používal zvýrazňovač syntaxe.