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++.
BoostMå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.
FOREACHMå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;
}
}
TupleVä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.
AnyMan 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 pointersGenom 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.
ThreadBoost 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_castOfta 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 bindboost::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++.