C++ Tips : rev. 2005-06-18 by FipS
Na tomto miste bych rad shromazdoval ruzne postrehy a zajimavosti tykajici se kazdodenni prace
s C++.
Tip #3 - "std::for_each" a funkcni adapter "std::mem_fun", 2005-06-18
V dnesni dobe si lze jen tezko predstavit rozsahlejsi aplikaci napsanou v C++ bez vyuziti
kolekci STL. Kolekce pak typicky realizuji uloziste instanci nejruznejsich objektu
nebo ukazatelu na instance. Pri praci s temito kolekcemi je casto nutne vyvolat
nejakou operaci u vsech ulozenych instanci, coz se typicky provadi volanim prislusne
clenske funkce v cyklu.
STL vsak nabizi elegantnejsi a potencialne efektivnejsi zpusob
s vyuzitim algoritmu "std::for_each" a funkcniho adapteru "std::mem_fun" nebo
"std::mem_fun_ref". Algoritmus "std::for_each" vola uzivatelsky definovanou funci v
urcitem rozsahu iteratoru, funkcni adapter "std::mem_fun..." prevadi funkcni volani na
volani clenske funkce objektu. Rozdil mezi "std::mem_fun" a "std::mem_fun_ref"
spociva v tom, ze prvni varianta pradpoklada kolekci ukazatelu na instance objektu,
zatimco druha kolekci vlastnich instanci. "std::mem_fun..." umoznuje volat
pouze clenske funkce bez argumentu. Pomoci dalsiho funkcniho adapteru "std::bind2nd"
docilime volani clenske funkce s jednim argumentem, volani s vice argumenty neni
v soucasne norme STL mozne. Priklad nize demonstruje uvedenou techniku.
Pozn. Knihovna [ Boost ] nabizi alternativu
k "std::mem_fun..." umoznujici volani clenskych funkci s neomezenym poctem argumentu.
#include <stdio.h>
#include <vector>
#include <algorithm> // std::for_each
#include <functional> // std::mem_fun, std::mem_fun_ref, std::bind2nd
struct STest
{
void Print() { printf("0x%08x\n", (size_t)this); }
void PrintValue(int nValue) { printf("nValue = %d\n", nValue); }
};
int main()
{
// vector of object instances
std::vector<STest> Tests;
Tests.push_back(STest());
Tests.push_back(STest());
Tests.push_back(STest());
// vector of object instance pointers
std::vector<STest *> TestPtrs;
TestPtrs.push_back(&Tests[0]);
TestPtrs.push_back(&Tests[1]);
TestPtrs.push_back(&Tests[2]);
printf("std::mem_fun_ref:\n");
// no argument call
std::for_each(Tests.begin(), Tests.end(),
std::mem_fun_ref(&STest::Print));
// 1 argument call
std::for_each(Tests.begin(), Tests.end(),
std::bind2nd(std::mem_fun_ref(&STest::PrintValue), 10));
printf("\nstd::mem_fun:\n");
// no argument call
std::for_each(TestPtrs.begin(), TestPtrs.end(),
std::mem_fun(&STest::Print));
// 1 argument call
std::for_each(TestPtrs.begin(), TestPtrs.end(),
std::bind2nd(std::mem_fun(&STest::PrintValue), 10));
return 0;
}
// std::mem_fun_ref:
// 0x00322c70
// 0x00322c71
// 0x00322c72
// nValue = 10
// nValue = 10
// nValue = 10
//
// std::mem_fun:
// 0x00322c70
// 0x00322c71
// 0x00322c72
// nValue = 10
// nValue = 10
// nValue = 10
|
[ Comments here... ]
Tip #2 - Clenske reference a "operator =", 2005-01-16, rev. 2005-01-18
Ve snaze vyprodukovat robustni kod, muzeme dospet do situace, kdy se v objektech vyskytnou
clenske polozky ve forme referenci (v prikladu viz. nize "m_rnValue"). Takove
polozky je mozne inicializovat pouze v konstruktoru. Problem nastava, potrebujeme-li
implementovat operator prirazeni "operator =", ktery je nutny napr. pokud chceme takovy objekt
ukladat do kolekci v STL. Prirazeni zpusobem "m_rnValue = RHS.m_rnValue"
predstavuje pouze kopirovani hodnoty, nikoliv samotne reference! (pro konstantni reference neni
mozne vubec). V tomto pripade se nabizi zajimave reseni, pri kterem je
explicitne vyvolan destruktor objektu a pote na puvodni adrese zkonstruovana nova instance
(pomoci placement new), jak je videt nize (tuto metodu je rovnez mozne vyuzit pro zachovani
konzistence mezi kopirovacim konstruktorem a operatorem prirazeni). Nutno podotknout, ze tento
zpusob prinasi urcite komplikace pri dedicnosti objektu, kdy je nutne operator prirazeni vhodne
implementovat u vsech potomku.
class CTest
{
public:
CTest(const int &nValue): m_rnValue(nValue) {}
CTest(const CTest &RHS) : m_rnValue(RHS.m_rnValue) {}
CTest & operator = (const CTest &RHS)
{
if(this != &RHS)
{
// wrong !!! (for 'const' not possible at all)
// m_rnValue = RHS.m_rnValue;
this->CTest::~CTest();
new (this) CTest(RHS);
}
return *this;
}
private:
CTest();
const int &m_rnValue;
};
int main()
{
int nValue1 = 1;
int nValue2 = 2;
CTest Test1(nValue1); // Test1.m_rnValue = 1
CTest Test2(nValue2); // Test2.m_rnValue = 2
Test2 = Test1; // call 'operator ='; Test(1/2).m_rnValue = 1
nValue1 = 3; // Test(1/2).m_rnValue = 3
return 0;
}
|
[ Comments here... ]
Tip #1 - Uskali bitovych posunu, 2005-01-16
Kazdy jiste nekdy provadel nejruznejsi bitove operace, nejcasteji zrejme za ucelem prace s tzv.
"flagy", kdy jednotlive bity nejakeho ciselneho typu predstavuji binarni informace ano/ne,
nebo jinou binarni aritmetiku, pri ktere bylo nutne vyuzivat operatory bitoveho posunu "<<"
nebo ">>".
Priklad nize naznacuje chovani techto operatoru. Operator bitoveho posunu vlevo "<<"
vykazuje transparentni chovani: vsechny bity jsou posunuty vlevo a nove vznikle misto je
doplneno, prislusnym poctem 0.
U operatoru bitoveho posunu vpravo ">>" je ovsem situace slozitejsi, coz by na prvni pohled
nemuselo byt zrejme! Zalezi totiz na tom, zda-li je levy operand typ znamenkovy - signed
nebo bezznamenkovy - unsigned. V pripade znamenkoveho typu je totiz znamenko reprezentovano
nejvyssim bitem, proto je pri bitovem posunu vpravo (ve snaze zachovat znamenko celeho ciselneho
typu) pouzit znamenkovy bit k vyplneni vznikle mezery.
Z techto duvodu se zda byt pro bitove operace vhodnejsi vyuzivat bezznamenkove - unsigned typy...
template<typename T>
void fsPrintBits(T t)
{
for(int i = 8 * sizeof T - 1; i >= 0; --i)
putchar((t & (1 << i)) ? '1' : '0');
putchar('\n');
}
int main()
{
unsigned char nUnsigned = 0xF0;
char nSigned = 0xF0;
fsPrintBits(nUnsigned); // 11110000
fsPrintBits(nSigned); // 11110000
unsigned char nUnsignedLeft = nUnsigned << 2;
char nSignedLeft = nSigned << 2;
fsPrintBits(nUnsignedLeft); // 11000000
fsPrintBits(nSignedLeft); // 11000000
unsigned char nUnsignedRight = nUnsigned >> 2;
char nSignedRight = nSigned >> 2;
fsPrintBits(nUnsignedRight); // 00111100 !!!
fsPrintBits(nSignedRight); // 11111100 !!!
return 0;
}
|
[ Comments here... ]