Deprecated: mysql_connect(): The mysql extension is deprecated and will be removed in the future: use mysqli or PDO instead in /home/www/novacisko.cz/subdomains/bredy/init.php on line 11

Warning: mysql_connect(): Headers and client library minor version mismatch. Headers:50562 Library:100020 in /home/www/novacisko.cz/subdomains/bredy/init.php on line 11

Warning: Cannot modify header information - headers already sent by (output started at /home/www/novacisko.cz/subdomains/bredy/init.php:11) in /home/www/novacisko.cz/subdomains/bredy/index.php on line 38
Tuples v C++ - Bredyho blog - Ondřej Novák
Bredyho blog - Ondřej Novák

Tuples v C++

Pokud si pamatujete na seriál Funkce s volitelným počtem argumentů v C++, tak v této části najdete jakési pokračování, i když cílem článku je trošku něco jiného


Upozornění na aktualizaci článku

Tento článek byl aktualizován, viz Tuple v C++ ver. 2

Co je to Tuple

Budeme hovořit o tzv. Tuple. Jedná se zpravidla o objekt představující víc než jednu hodnotu. Nejblíže má k poli, avšak narozdíl od pole, lze do Tuple uložit hodnoty libovolných typů. S objektem lze posléze manipulovat tak jak je obvyklé, zpravidla jej lze kopírovat a předávat a samozřejmě přistupovat k jednotlivým položkám.

Trochu rozsáhlejší definici Tuple naleznete na Wikipedii

V C++ nenajdeme Tuple jako datový typ, jako je tomu třeba u Pythonu. Nenajdeme jej ale ani v STL, i když něco jako Tuple zde představuje objekt pair. Pair je objekt, který uloží libovolný pár hodnot libovolného typu. Pair lze i vnořovat do sebe, takže se pair může skládat z páru objektů typu pair, které obsahují data. Dostaneme tak "tuple" obsahující 4 hodnoty

typedef std::pair<std::pair<int, long>,std::pair<std::string, double> > NejakyTuple4;

Pro snadnou tvorbu tuple pomocí objektu pair můžeme využit šablonu make_pair

 std::make_pair(std::make_pair(10,20L),std::make_pair("ahoj",3.2));
//vyrob tuple pro 4 hodnoty.

Uživatelský komfort není velký.

Inspirace: VarArgs

V článku Funkce s volitelným počtem argumentů v C++ II jsem představil způsob, jak předávat libovolný počet argumentů do funkce bez použití tří teček. Z toho můžeme vyjít. Opět tedy budeme vytvářet seznam tříd a parametrů pomocí ukládání hlavičky a tělíčka.

Nebudu se rozepisovat o celém principu, protože se moc neliší, raději přejdu rovnou k výsledkům.

tuples.h Zdrojový kód pro implementace Tuples

Použití tuples v programech

Nejprve malé upozornění, všechny deklarace jsou uvnitř namespace BredyLibs. V dalším výkladu předpokládám globální deklaraci using namespace BredyLibs. Pokud vám tato deklarace nevoní (což celkem chápu), udělejte si to podle sebe, jen následující příklady je nutné adekvátně upravit.

Dva druhy tuples

Při používání tuples je nutné provést rozhodnutí, jakou implementaci použijeme

  1. const-ref-based tuple
  2. value-based tuple

V první případě jde o variantu, kdy všechny hodnoty v tuple jsou pouze referencované přes const T &. V druhém případě se hodnoty ukládají přímo do objektu.

Jaký je v tom rozdíl? Jde zejména o výkonnostní. V první případě je manipulace s tuplem velice rychlá, protože při jakékoliv manipulaci se kopírují pouze ukazatelé. Takové tuple však není možné uložit, protože stále vyžaduje původní objekty. V druhém případě se objekty ukládají přímo do tuple, takže tuple lze ukládat, ale manipulace s tuplem znamená kopírování objektů.

Deklarace tuple

Pokud si vzpomínáte na VarArgs, víte, že deklarovat takový objekt je obtížné. Tuple je tvořeno pomocí rekurzivního seznamu. Deklarace čtyřparametrového tuple vypadá následovně

Tuple<int,Tuple<long,Tuple<const char *,Tuple<double,EmptyTuple> > > >

Nicméně v programu se budeme takovýmto deklaracím vyhýbat. Postačí, když nám takové tuple někdo vyrobí, a my jej použijeme. Přitom Tuple můžeme použít prakticky jen jako parametr šablonované funkce, tak aby si překladač typ tuple odvodil sám

Deklarace funkce přijímající tuple

Aby funkce byla schopna přijmout tuple, musí se jednat o šablonu

template<class H, class T>
void foo(const Tuple<H,T> &t)
{

}

Typy H a T jsou pomocné a překladač si za ně dosadí ty správně reprezentanty. H je vždy typ posledního prvku. T vždy představuje Tuple všech parametrů, kromě posledního.

Vytvoření tuple

Aby vytváření tuple bylo co nejkonformnější, využívá knihovna sadu přetížených operátorů tak, aby tuple co nejvíc připomínalo parametry funkce.

(tuple|p1,p2,p3,p4,...,pn)

Výše uvedeným způsobem vytvoříme tuple, včetně na míru vytvořené třídy. Klíčové slovo tuple je ve skutečnosti enum hodnota, nicméně, překladač podle ní rozpozná operátor | a založí řetězec tuple. Do řetězce se pak přidává přes operátor čárky.

Celá deklarace je přitom musí být v závorkách. To proto, že operátor čárky má nejnižší prioritu a tak by překladač nemusel pochopit co se od něj žádá. Navíc, pokud tuto deklaraci použijeme jako parametr funkce, závorky jsou nezbytné.

 foo((tuple|10,20,"ahoj",3.2));

Práce s tuple

Jakmile je zavolána funkce přijímající Tuple, máme k dispozici spoustu funkcí, jak doslova vydolovat informace z tuple. Je zřejmé, že klasický přístup jako do pole nebude v tomto případě možný. Zejména proto, že dopředu neznáme typ, které budou v tuple použity.

Typ každého prvku tuple můžeme pomocí typové funkce (struktury) TupleGet

 typename TupleGet<Tuple<H,T>,index>::Type

Index prvku zadáváme jako parametr šablony, takže musí to být konstanta. Hodnotu takového prvku zjistíme pomocí funkce Value. Následuje úplná deklarace...

typename TupleGet<Tuple<H,T>,index>::Type hodnota =  TupleGet<Tuple<H,T>,index>::Value(tuple)

Šílené? Určitě ano. Naštěstí pokud už máme funkci pracující s tuple, pravděpodobně nebudeme potřebovat vyzvedávat prvky po jednom. Mnohem časteji budeme chtít prvky tuple procházet. K tomu poslouží funkce ForEach


ForEach(tuple, funkce)

Funkce zavolá funkci pro každý prvek tuple. Je zřejmé, že to musí být šablona. Je také možné, aby místo funkce byl použit funktor. Další možností je sada funkcí, pro předem známé typy. Tím lze omezit množství použitých typů v tuple. Překladač sám zajistí, aby nebyl přeložen program, pro které nebude známa funkce pro každý typ každého prvku tuple.

Variantu na ForEach ale s možností specifikovat jeden prvek je funkce ForIndex

ForIndex(tuple, index, funkce).

Zde platí, že ForIndex zavolá funkci pouze pro jeden index v tuple.

Máme-li k dispozici H a T, můžeme přistupovat k tuple i jinak.

//H obsahuje přímo typ posledního prvku
H posledni = tuple.GetHead();
typename T::GetHeadType predposledni = tuple.GetTail().GetHead();
typename T::GetTailType::GetHeadType predpredposledni = tuple.GetTail().GetTail().GetHead();

atd...

Převod const-ref-based tuple na value-based tuple

Tuple vytvářené pomocí klíčového slova (tuple|...) jsou v konstruována jako const-ref-based. Tyto tuple nemůžeme uložit ani kopírovat, protože se stále odkazují na původní objekty pomocí reference. K převodu na value-based tuple lze použít jednoduchý zápis

typename TupleDeRef<Tuple<H,T> >::Result vbtuple(tuple);

Tímto zápisem vytvoří překladač shodný typ, který pouze všechny const X & reference nahradí jednoduchým typem X. Konstrukcí takového objektu z původního tuple se provede pouze kopírování původních prvků do prvků alokovaných v tuple.

Optimalizace překladačem

Protože ve všech případech se jedná o šablony, překladač by měl provést optimalizaci výsledného kódu tak, aby výsledkem byl co nejrychlejší kód. Přesvědčme se sami:

#include <iostream>
#include "tuples.h"
using namespace std;
using namespace BredyLibs;

struct PrintIt
{
    mutable int param;
    PrintIt():param(0) {}

    bool operator()(const char *x) const {
        cout << param++ <<". parameter type text: " << x << endl;
        return false;
    }

    bool operator()(int x) const {
        cout << param++ <<". parameter type int: " << x << endl;
        return false;
    }

    bool operator()(double x) const {
        cout << param++ <<". parameter type double: " << x << endl;
        return false;
    }

    bool operator()(bool x) const {
        cout << param++ <<". parameter type boolean: " << (x?"true":"false") << endl;
        return false;
    }
};

template<class H, class T>
void PrintTest(const Tuple<H,T> &params)
{
    PrintIt printIt;
    ForEach(params,printIt);
}

int main() {


    PrintTest((tuple|10,"Ahoj",6.0,false,"pokus"));
    return 0;
}

Vytvoření tuple v příkladu.

PrintTest((tuple|10,"Ahoj",6.0,false,"pokus"));
004022A6 fld qword ptr [__real@4018000000000000 (403350h)] 
004022AC lea edx,[esp+18h]
004022B0 fstp qword ptr [esp+18h]
004022B4 lea eax,[esp+0Ch]
004022B8 mov dword ptr [esp+34h],edx
004022BC lea edx,[esp+10h]
004022C0 mov dword ptr [esp+2Ch],eax
004022C4 push edx
004022C5 lea eax,[esp+30h]
004022C9 mov ecx,offset string "Ahoj" (403348h) 004022CE lea esi,[esp+0Eh]
004022D2 push eax
004022D3 mov byte ptr [esp+12h],0
004022D8 mov dword ptr [esp+14h],0Ah
004022E0 mov dword ptr [esp+38h],ecx
004022E4 mov dword ptr [esp+40h],esi
004022E8 mov dword ptr [esp+44h],offset string "pokus" (403340h)
004022F0 mov dword ptr [esp+18h],0
004022F8 call BredyLibs::ForEach<char const *,BredyLibs::Tuple<bool const &,BredyLibs::Tuple<double const &,BredyLibs::Tuple<char const *,BredyLibs::Tuple<int const &,BredyLibs::EmptyTuple> > > >,PrintIt const &> (401E80h)

K čemu je to dobré

Určitě je to k něčemu dobré, pokud o tom píšu. Umožnit ukládat parametry do objektu se bude hodit při návrhu objektů Action a Message. Kromě toho samozřejmě lze tuple využit při předávání parametrů libovolného počtu a typu a samozřejmě je vrace z funkcí, i když tam je potřeba deklarovat návratový typ ručně.

vytvořeno: 15.8.2007 00:46:30, změněno: 2.4.2008 10:00:12
Jsou informace v článku pro Vás užitečné?
  • (3)
  • (0)
  • (0)
  • (0)
  • (1)
Nick nebo OpenID
Vzkaz
 
25.7.2016 20:02:53

MwWK32YLnQ

Haha, essa vaga é um troll disfarçado, certeza A ironia é que, se essa vaga fosse real, quando a coisa estivesse &#e;a0;f8i2&#82212, a empresa iria acabar cortando esse monte de requisitos gigantesco e contratando alguém com a skill específica que eles precisassem.Eles pedem o Hulk, mas acabam ficando com o Iron Man sem armadura Abraços!
25.7.2016 18:31:26

w6FJ2muUIz

we would be in a long term bear market and that the Dow would hit new lows. he prtedcied Dow 400 for the 90s. he was clearly wrong. the 87 low was the low and the 90s saw a great bull market.
2.7.2014 22:51:48

kMoaazjvY

right. it’s sort of case-by-casewhy it’s useful. the exlapme I used here is a made up simple one, in the real exlapmes there’s more of a rationale for the separation.they are semantically distinct really. the “unbound” instance conceptually gets its context from the lexical scope, so the context is a property of the thread or at least the control flow. The bound instance makes the context a property of the instance. Both can be useful or awkward in different scenarios. For exlapme the unbound instance might be better for a singleton, but the bound instance might be better to pass around as a parameter.

Podobné články

Distributor - vaše objekty budou o svém okolí vědět vše

Největším problémem většiny UI /ale i ne-IU/ aplikací, které potkávám, je špatná nebo vůbec nevyřešená komunikace mezi důležitými objekty. Přitom stačí málo, obstarat si distributora.

Akce a Zpráva jako objekt v.2

V článku C++ - Akce a Zpráva jako objekt jsem představil dva objekty, jenž slouží k objektovému zabalení volání funkce nebo metody. V tomto článku si představíme vylepšenou druhou verzi.

Tuple v C++ ver. 2

Druhá verze implementace tuplů v C++. Navazuje na článek Tuples v C++

C++ - Akce a Zpráva jako objekt

Akcí nazývám obyčejné volání funkce, zprávou pak volání metody objektu (jak je chápáno podle OOP). Lze vůbec volání čehokoliv reprezentovat jako objekt? A k čemu je to vlastně dobré?
Reklama: