söndag 19 april 2009

Från hanterad kod till C++, del 3

Detta är del 3 i inläggsserien om att gå från .NET/Java till C++. Förra delen handlade om olika typer av keywords och mekanismer i C++ som är krångliga. Denna del ska handla om Boost, det bästa tillägget till Standard C++.

Boost
Många lär sig tidigt att använda Standard Template Library, STL, till C++. STL inkluderar många klasser man använder dagligen t ex std::vector, std::map och std::string, för att nämna de vanligaste. STL är helt okej, men det finns en ramverk som delvis ersätter och delvis kompletterar STL, nämligen Boost.

Boost underhålls av några mycket tunga namn från C++ världen, många som även sitter med i C++ standardkommite. Detta har resulterat i att flera Boostklasser nu är en del av Standard C++.

Jag tycker att Boost är det absolut bästa komplementet till C++, och många av Boost klasser som ersätter STL är betydligt bättre än originalet. Boost är också mycket trevligt att använda; den mesta koden ligger direkt i header-filerna så man behöver ofta inte ens lib-filerna. Självklart är också hela Boost plattformsoberoende.

För att visa hur trevliga Boostbiliboteken är presenterar jag några klasser som vi ofta använder på jobbet.


FOREACH
Många språk (t ex C# och Perl) har en inbyggd foreach funktionalitet för att iterera igenom listor. I C++ finns det flera olika försök till att implementera detta, men Boost har lyckats bäst. Syntaxen är väldigt smidig och mycket lik den i C#:

#include <boost/foreach.hpp>

// skriver ut "2" på skärmen

void main()
{
std::vector<int> vec;
vec.push_back(2);

BOOST_FOREACH(int i, vec)
{
std::cout << i << std::endl;
}
}

Tuple
Väldigt ofta vill man på ett enkelt sätt associera två eller fler objekt med varann. T ex kanske man bara snabbt vill få ut en x,y punkt ur en funktion, och man orkar inte skapa en klass eller struct bara för att uppnå detta. Då är tuples perfekta. Följande exempel visar hur man skapar tuples och hämtar värdena i en tuple.

#include <boost/tuple/tuple.hpp>


boost::tuples::tuple<std::string, int> getPerson()
{
boost::tuples::tuple<std::string, int> point_tuple("Per", 20);
return point_tuple;
}

void main()
{
boost::tuples::tuple<std::string, int> pers = getPerson();

std::string name = boost::tuples::get<0>(pers);
int age = boost::tuples::get<1>(pers);
}

Om man inte vill ange typerna då man skapar en tuple kan man låta Boost gissa dem genom att använda make_tuple (exemplet taget från boost.org):


tuple<int, int, double> add_multiply_divide(int a, int b)
{
return make_tuple(a+b, a*b, double(a)/double(b));
}

Det finns massor av mer funktionalitet kring tuple, som t ex jämförelser och input/output. tuples lämpar sig bra tillsammans med en typedef eller två, då uttrycken tenderar att bli långa.


Any
Man vill ofta ha ett sätt att lagra helt olika, orelaterade typer i en lista eller annan behållare. Det kan röra sig om inställningar, kolumner från en databas, funktionspekare eller vad som helst. Genom åren har man använt sig av flera olika alternativ för att göra detta. Ett sätt är att kasta allt till void*, vilket är ett väldigt osäkert och fult sätt. Man kan även använda sig av union eller någon form av arvstruktur med en generell basklass.

Det överlägset bästa sättet är att använda sig av boost::any. boost::any lagrar på ett typsäkert sätt vilken typ som helst. Se följande exempel för deklarationen:

#include <boost/any.hpp>

void main()
{
std::vector<boost::any> anyValues;

int i = 40;
std::string s = "hej";

boost::any anyInt = i;
boost::any anyString = s;

anyValues.push_back(anyInt);
anyValues.push_back(anyString);
}
För att avgöra vilken typ en boost::any har använder man metoden type():

BOOST_FOREACH (boost::any a, anyValues)
{
if (a.type() == typeid(int))
int i = boost::any_cast<int>(a);
else if (a.type() == typeid(std::string))
std::string s = boost::any_cast<std::string>(a);
}

Smart pointers

Genom mekanismen new/delete tvingas man som programmerare att komma ihåg att avallokera minne vid rätt tidpunkt i C++. Detta är något helt främmande för oss som använder hanterade språk där Garbage Collector automatisk avallokerar minne som inte längre används.

Sån tur är behöver man inte tänka på sådant i C++ längre; det finns flera implementationer av s.k smarta pekare som ser till att minnet frigörs automatiskt. Den vanligaste är boost::shared_ptr. Den använder referensräkning för att hålla reda på när ett objekt kan deallokeras.

Genom att alltid göra som i exemplet nedan då du tänkte skriva "new" slipper du helt minnesläckor associerade med dynamisk minnerallokering:

#include <boost/shared_ptr.hpp>

class MyClass
{
public:
void memberFunction() {;};
};

typedef boost::shared_ptr<MyClass> MyClassPtr; // bra med typedef då det tenderar bli långa rader


void main()
{
MyClassPtr myclass(new MyClass()); // istället för bara "new"

myclass.get()->memberFunction(); // get() returnerar en vanlig pekare till klassen så medlemmar kan anropas

} // här kommer myclass automatiskt att raderas

Om man vill skapa en smart pointer till en array bör man använda shared_array. shared_array är egentligen identisk med shared_ptr, men den använder sig av new[] och delete[], som sig bör för pekare till arrayer.

En sak man måste tänkta på är att aldrig stoppa en smart-pointer inuti en annan smart-pointer. Detta kommer att resultera i att boost försöker deallokera objektet två gånger, vilket ger ett runtime-fel. Man ska också komma ihåg att det inte bara är boost som implementerar smart-pointers; därför måste man se till att det man stoppar in i en boost::shared_ptr inte redan är "smart-pointifierad" sedan tidigare om man använder något externt API.


Thread

Boost definierar en plattformsoberoende trådmekanism, boost::thread. Det trevliga är just att den är plattformsoberoende; man slipper använda Windows thread, pthreads eller något GUI-specifikt trådbibliotek (MFC, GTK och QT har alla egna trådklasser).

Självklart implementeras boost::thread med antingen Windows threads eller pthreads beroende på system, men detta är inget vi behöver bry oss om. Enklaste tänkbara exemplet:

#include <boost/thread/thread.hpp>

void func()
{
}


void main()
{
boost::thread thrd(&func);
}

Ovan startar omedelbart en tråd som börjar exekvera func().
Ibland vill vi skicka in ett argument till funktionen som ska köras. Innan boost 1.35 var det lite krångligare, men nu kan man helt enkelt skriva:

#include <boost/thread/thread.hpp>

void func(int inArg)
{
// kallas med inArg = 243
}

void main()
{
boost::thread thrd(func, 243);
}
Det är inga problem att knyta en medlemsfunktion till en tråd:

#include <boost/thread/thread.hpp>

class MyClass
{
public:
void fun(int inarg)
{
// ...

}
void runThread()
{
boost::thread(&MyClass::fun, this, 243);
}
int m_i;
};
Förutom detta finns en uppsjö med olika mutex att använda för att trådsäkra och synkronisera din kod. Se boost dokumentation.


lexical_cast

Ofta vill man kunna röra sig mellan siffror och strängar på ett smidigt sätt. Tex vill man kanske översätta

std::string = "3.5"

till en double eller int vid senare tillfälle. Det finns lite olika sätt att göra detta. Man kan använda atoi, atof, sprintf osv osv. Boost definierar istället en enda cast, lexical_cast, för allt sådant. Koden nedan är självförklarande.

#include <boost/lexical_cast.hpp>

void main()
{
std::string s = "3.5";

double d = boost::lexical_cast<double> (s);
int i = boost::lexical_cast<int> (s);
std::string s2 = boost::lexical_cast<std::string> (i);
}

function och bind

boost::function och boost::bind är två fantastiska verktyg för att (bland annat) skapa och använda funktionspekare och att ändra funktioners aritet. Koncepten är hyfsat avancerade och förtjänar ett helt eget inlägg. Just nu kan jag bara nämna att detta är vad du ska använda då du behöver funktionspekare eller unära funktioner i C++.

Inga kommentarer:

Skicka en kommentar