Array di Parametri nel C++

_Achille

Utente Attivo
2,881
647
Hardware Utente
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
Hard Disk
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
Scheda Video
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
Alimentatore
RM550X
Case
NZXT S340
Periferiche
Cooler Master XT; Razer Abyssus
Sistema Operativo
Windows 10 Pro
#1
Sera ragazzi.
Studiando il C# ho scoperto che è possibile dichiarare una funzione che accetta un numero indefinito di parametri che va a spiegare il metodo statico System.Console.Write(...).
Confrontandolo col C++ mi è venuto in mente la gestione degli stream del C. Ad esempio la funzione printf(const char*, ...) accetta parametri illimitati. Cercando però il prototipo molto spesso è scritto come io ho scritto sopra, ovvero la stringa e poi i 3 puntini. Ipoteticamente quei tre puntini non indicano alcun tipo :asd:
Mi chiedevo quindi se, visto che è possibile in C, in C++ è possibile avere un Array/Lista di argomenti.

Grazie
 
397
254
Hardware Utente
CPU
Intel i7 6700HQ, 2.60Ghz, 4 core 8 threads
Scheda Madre
Asustek
Hard Disk
Hitachi 7200 rpm, 1TB
RAM
16GB DDR4 (2 slot su 4)
Scheda Video
Nvidia Geforce GTX 960M, 4GB
Scheda Audio
Realtek
Sistema Operativo
Windows 10 64bit
#3
Mi Piace: _Achille

_Achille

Utente Attivo
2,881
647
Hardware Utente
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
Hard Disk
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
Scheda Video
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
Alimentatore
RM550X
Case
NZXT S340
Periferiche
Cooler Master XT; Razer Abyssus
Sistema Operativo
Windows 10 Pro
#4
Grazie mille questo è proprio quello che le funzioni standard del C utilizzano
Fantastico! Esiste quindi qualche implementazione del C++. Questa però pare più complessa
 
345
111
Hardware Utente
Sistema Operativo
Windows 10
#5
Mi chiedevo quindi se, visto che è possibile in C, in C++ è possibile avere un Array/Lista di argomenti.
Grazie
Certo che si, infatti ogni volta che inizio un progetto la prima cosa che faccio è scrivermi una funzione che fà il std::cout su un numero arbitrario di elementi, un pò come il printf() ma senza la roba %d %s etc... che è orribile da vedere (imo). Stiamo parlando di variadic template argument (nel caso vuoi cercare su google).

Per esempio:
C++:
void ConOut()
{
    std::cout << endl;
}

template<typename F, typename... Rest>
void ConOut(F first, Rest... rest)
{
    std::cout << first << " ";
    ConOut(rest...);
}
Nota che le 2 funzioni hanno lo stesso nome, per cui sono 2 overload e quella che viene chiamata dipende da quanti argomenti passi quando chiami ConOut(), ovvero se passi nulla chiami il primo overload, se passi uno o piu argomenti viene scelto il secondo overload.
typename... Rest è un parameter pack che può contenere uno o piu elementi a seconda di quanti decidi di passarne.
Percui se chiamo ConOut("wow", "much template", "very arguments", 42) il secondo overload viene chiamato, il "wow" diventa il "first" mentre gli altri 3 diventano il "rest", cout scrive "wow" nella console e la funzione chiama se stessa in maniera recursiva passando i 3 parametri rimanenti, e cosi via fino a che il first è l'ultimo elemento passato (42 nell'esempio) e rest è vuoto (si, un parameter pack può essere vuoto), e quando ConOut(rest...) è chiamata passando un parameter pack vuoto, il primo overload della funzione viene eseguito che non fà altro che andare a capo.

Se quanto scritto sopra non ti ha confuso, una maniera più avanzata è concisa di creare la stessa funzione sopra è Fold Expression come nell'esempio sotto (ricorda di compilare per C++17):
Codice:
template<typename... T>
void ConOut(T... args)
{
    (std::cout << ... << args);
}
In questo caso, una sola funzione è sufficiente. La pattern "... operatore pack" è la Fold Expression (ne esistono diverse, ad esempio i ... a destra)
La riga (std::cout << ... << args); viene espansa in questa maniera quà (assumendo che passiamo 4 argomenti alla chiamata della funzione):
((((std::cout << arg1) << arg2) << arg3) << arg4) e quindo non c'è nessuna recursione.
 
Ultima modifica:
Mi Piace: _Achille

_Achille

Utente Attivo
2,881
647
Hardware Utente
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
Hard Disk
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
Scheda Video
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
Alimentatore
RM550X
Case
NZXT S340
Periferiche
Cooler Master XT; Razer Abyssus
Sistema Operativo
Windows 10 Pro
#6
Certo che si, infatti ogni volta che inizio un progetto la prima cosa che faccio è scrivermi una funzione che fà il std::cout su un numero arbitrario di elementi, un pò come il printf() ma senza la roba %d %s etc... che è orribile da vedere (imo). Stiamo parlando di variadic template argument (nel caso vuoi cercare su google).

Per esempio:
C++:
void ConOut()
{
    std::cout << endl;
}

template<typename F, typename... Rest>
void ConOut(F first, Rest... rest)
{
    std::cout << first << " ";
    ConOut(rest...);
}
Nota che le 2 funzioni hanno lo stesso nome, per cui sono 2 overload e quella che viene chiamata dipende da quanti argomenti passi quando chiami ConOut(), ovvero se passi nulla chiami il primo overload, se passi uno o piu argomenti viene scelto il secondo overload.
typename... Rest è un parameter pack che può contenere uno o piu elementi a seconda di quanti decidi di passarne.
Percui se chiamo ConOut("wow", "much template", "very arguments", 42) il secondo overload viene chiamato, il "wow" diventa il "first" mentre gli altri 3 diventano il "rest", cout scrive "wow" nella console e la funzione chiama se stessa in maniera recursiva passando i 3 parametri rimanenti, e cosi via fino a che il first è l'ultimo elemento passato (42 nell'esempio) e rest è vuoto (si, un parameter pack può essere vuoto), e quando ConOut(rest...) è chiamata passando un parameter pack vuoto, il primo overload della funzione viene eseguito che non fà altro che andare a capo.

Se quanto scritto sopra non ti ha confuso, una maniera più avanzata è concisa di creare la stessa funzione sopra è Fold Expression come nell'esempio sotto (prima di compilare per C++17):
Codice:
template<typename... T>
void ConOut(T... args)
{
    (std::cout << ... << args);
}
In questo caso, una sola funzione è sufficiente. La pattern "... operatore pack" è la Fold Expression (ne esistono diverse, ad esempio i ... a destra)
La riga (std::cout << ... << args); viene espansa in questa maniera quà (assumendo che passiamo 4 argomenti alla chiamata della funzione):
((((std::cout << arg1) << arg2) << arg3) << arg4) e quindo non c'è nessuna recursione.
Cavolo se è complessa sta roba! Grazie mille
PS: va detto che comunque non riesco a far funzionare alcun codice
 
Ultima modifica:
345
111
Hardware Utente
Sistema Operativo
Windows 10
#7
PS: va detto che comunque non riesco a far funzionare alcun codice
Il codice sotto (e sopra) funziona, appena testato sul mio pc, assicurati di star compilando per C++17

C++:
#include <iostream>
using namespace std;


void ConOut()
{
    cout << endl;
}

template<typename F, typename... Rest>
void ConOut(F first, Rest... rest)
{
    cout << first << " ";
    ConOut(rest...);
}

template<typename... T>
void FoldedVariation(T... args)
{
    (cout << ... << args);
}

int main()
{

    ConOut("wow", "much template", "very arguments", 5.63);

    FoldedVariation("wow", "much template", "very arguments", 42'000, '\n');
    return 0;

}
 

_Achille

Utente Attivo
2,881
647
Hardware Utente
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
Hard Disk
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
Scheda Video
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
Alimentatore
RM550X
Case
NZXT S340
Periferiche
Cooler Master XT; Razer Abyssus
Sistema Operativo
Windows 10 Pro
#8
Il codice sotto (e sopra) funziona, appena testato sul mio pc, assicurati di star compilando per C++17

C++:
#include <iostream>
using namespace std;


void ConOut()
{
    cout << endl;
}

template<typename F, typename... Rest>
void ConOut(F first, Rest... rest)
{
    cout << first << " ";
    ConOut(rest...);
}

template<typename... T>
void FoldedVariation(T... args)
{
    (cout << ... << args);
}

int main()
{

    ConOut("wow", "much template", "very arguments", 5.63);

    FoldedVariation("wow", "much template", "very arguments", 42'000, '\n');
    return 0;

}
Tecnicamente usando VisualStudio2017 dovrei già avere VC++17 (anche perché ho già usato caratteristiche del C++17 che hanno sempre funzionato). Appena posso ti mando l’errore (che risiede nei tre puntini).
 

_Achille

Utente Attivo
2,881
647
Hardware Utente
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
Hard Disk
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
Scheda Video
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
Alimentatore
RM550X
Case
NZXT S340
Periferiche
Cooler Master XT; Razer Abyssus
Sistema Operativo
Windows 10 Pro
#10
A dire il vero mi pare di no, dovresti cliccare col destro sul tuo progetto, andare su Properties->C/C++ -> Language -> C++ Language Standard e scegliere ISO C++ Latest Draft Standard dalla lista
Cavolo è vero. Ora il codice funziona!
Bisogna solo capirlo ora :asd:
 
345
111
Hardware Utente
Sistema Operativo
Windows 10
#11
Se vuoi capire cosa sta succedendo, allora ti serve questa playlist qui (ed ovviamente familiarità con l'inglese) https://www.youtube.com/playlist?list=PL9QAu9zhcKhGEDZpDtk33mz1kRV9o1vfa

Nel corso di quei video lì spiega cose come "variadic template arguments", "template argument deduction", "overload resolution", "SFINAE", "ADL", etc che sicuramente ti dovrebbero dare un'idea chiara di cosa sta succedendo, inoltre copre anche altri topic come "RAII", "lambda expression", "casts", "smart pointers", "iterators" etc.. che ti servono comunque se vuoi capire meglio C++
Anche se non capisci tutto la prima volta che li vedi, di sicuro dovrebbe chiararti un sacco di idee al riguardo :)
 
Ultima modifica:
Mi Piace: _Achille

_Achille

Utente Attivo
2,881
647
Hardware Utente
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
Hard Disk
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
Scheda Video
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
Alimentatore
RM550X
Case
NZXT S340
Periferiche
Cooler Master XT; Razer Abyssus
Sistema Operativo
Windows 10 Pro
#12
Se vuoi capire cosa sta succedendo, allora ti serve questa playlist qui (ed ovviamente familiarità con l'inglese) https://www.youtube.com/playlist?list=PL9QAu9zhcKhGEDZpDtk33mz1kRV9o1vfa

Nel corso di quei video lì spiega cose come "variadic template arguments", "template argument deduction", "overload resolution", "SFINAE", "ADL", etc che sicuramente ti dovrebbero dare un'idea chiara di cosa sta succedendo, inoltre copre anche altri topic come "RAII", "lambda expression", "casts", "smart pointers", "iterators" etc.. che ti servono comunque se vuoi capire meglio C++
Anche se non capisci tutto la prima volta che li vedi, di sicuro dovrebbe chiararti un sacco di idee al riguardo :)
Innanzittutto ti ringrazio per il materiale su cui passerò un bel po di tempo a studiare. Diciamo che non mi sono mai fissato troppo sui template, più che altro li ho sottovalutati. I cast li ho fortunatamente studiati, iteratori con le classi contenitori, lamda studiando ora con C# mentre smartptr e RAII sono proprio lacune :muro:
 
345
111
Hardware Utente
Sistema Operativo
Windows 10
#13
A dire il vero credo che conosci gia RAII, anche se non con questo nome. RAII sta per "Resource Acquisition Is Initialization" ed è un idioma del linguaggio (con un nome terribile), un nome più utile per lo stesso idioma è Constructor Acquires, Destructor Releases (CADRe) che significa come puoi capire dal nome, che è responsabilità di un costruttore acquisire una risorsa (ad esempio memoria allocata nell'heap) ed è responsabilità del distruttore rilasciare quella risorsa (ad esempio chiamando delete[]).
Questo significa che la durata della tua risorsa che hai acquisito è strettamente collegata alla "lifetime" dell'oggetto che hai creato, quindi se non hai leak di oggetti, non hai neppure leak di risorse.

Esempio che non segue il principio RAII:
Codice:
int main()
{
    int* x = new int[500]();
 
    //cose...
     
    return 0;
}
Come vedi mi sono dimenticato di fare delete, ora ho una memory leak.

Esempio RAII:
Codice:
class NoLeak
{
public:
NoLeak(int s):memory(new int[s]()),size{s}{}
~NoLeak(){delete[] memory;}

private:
int* memory;
int size;
};

int main()
{
    NoLeak x(500);
 
    //cose...
     
    return 0;
Come vedi anche nel secondo esempio mi sono dimenticato di fare delete[], anche se a dire il vero non importa più perchè non posso davvero dimenticarmi, il distruttore lo fà sempre e comunque quanto raggiunge la fine del blocco di codice e l'oggetto che ho creato viene distrutto, percui posso dimenticarmi per sempre di fare delete[] e non ritrovarmi ad avere memory leak...a meno che non faccio qualcosa di stupido, ad esempio un leak degli oggetti stessi come nell'esempio sotto:
Codice:
int main()
{
    NoLeak* leakAssicurato = new NoLeak(500);

    //cose...

    return 0;
}
di nuovo mi sono dimenticato di fare delete, però stavolta ho un leak dell'intero oggetto, che essendo nell'heap non ha possibilità di avere il suo distruttore chiamato quando raggiungo la fine del blocco di codice. Percui ora dovresti vedere chiaramente che finchè si usano "raw pointers", ti stai esponendo al rischio di memory leak, e questo rende chiara l'importanza degli smart pointers:

Codice:
int main()
{
    unique_ptr<NoLeak> mioOggetto = make_unique<NoLeak>(500);

    //cose...

    return 0;
}
Di nuovo, mi dimentico di fare delete, ma quando unique_ptr raggiunge la fine del blocco e sta per essere distrutto, chiama il distruttore di NoLeak che a sua volta rilascia la memoria che aveva acquisito. E quindi no leaks :)
In sostanza questo è RAII.
 
Ultima modifica:

_Achille

Utente Attivo
2,881
647
Hardware Utente
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
Hard Disk
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
Scheda Video
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
Alimentatore
RM550X
Case
NZXT S340
Periferiche
Cooler Master XT; Razer Abyssus
Sistema Operativo
Windows 10 Pro
#14
A dire il vero credo che conosci gia RAII, anche se non con questo nome. RAII sta per "Resource Acquisition Is Initialization" ed è un idioma del linguaggio (con un nome terribile), un nome più utile per lo stesso idioma è Constructor Acquires, Destructor Releases (CADRe) che significa come puoi capire dal nome, che è responsabilità di un costruttore acquisire una risorsa (ad esempio memoria allocata nell'heap) ed è responsabilità del distruttore rilasciare quella risorsa (ad esempio chiamando delete[]).
Questo significa che la durata della tua risorsa che hai acquisito è strettamente collegata alla "lifetime" dell'oggetto che hai creato, quindi se non hai leak di oggetti, non hai neppure leak di risorse.

Esempio che non segue il principio RAII:
Codice:
int main()
{
    int* x = new int[500]();

    //cose...
    
    return 0;
}
Come vedi mi sono dimenticato di fare delete, ora ho una memory leak.

Esempio RAII:
Codice:
class NoLeak
{
public:
NoLeak(int s):memory(new int[s]()),size{s}{}
~NoLeak(){delete[] memory;}

private:
int* memory;
int size;
};

int main()
{
    NoLeak x(500);

    //cose...
    
    return 0;
Come vedi anche nel secondo esempio mi sono dimenticato di fare delete[], anche se a dire il vero non importa più perchè non posso davvero dimenticarmi, il distruttore lo fà sempre e comunque quanto raggiunge la fine del blocco di codice e l'oggetto che ho creato viene distrutto, percui posso dimenticarmi per sempre di fare delete[] e non ritrovarmi ad avere memory leak...a meno che non faccio qualcosa di stupido, ad esempio un leak degli oggetti stessi come nell'esempio sotto:
Codice:
int main()
{
    NoLeak* leakAssicurato = new NoLeak(500);

    //cose...

    return 0;
}
di nuovo mi sono dimenticato di fare delete, però stavolta ho un leak dell'intero oggetto, che essendo nell'heap non ha possibilità di avere il suo distruttore chiamato quando raggiungo la fine del blocco di codice. Percui ora dovresti vedere chiaramente che finchè si usano "raw pointers", ti stai esponendo al rischio di memory leak, e questo rende chiara l'importanza degli smart pointers:

Codice:
int main()
{
    unique_ptr<NoLeak> mioOggetto = make_unique<NoLeak>(500);

    //cose...

    return 0;
}
Di nuovo, mi dimentico di fare delete, ma quando unique_ptr raggiunge la fine del blocco e sta per essere distrutto, chiama il distruttore di NoLeak che a sua volta rilascia la memoria che aveva acquisito. E quindi no leaks :)
In sostanza questo è RAII.
Ah sì diciamo che già lo capito quando avevo implementato una classe Array. Più che altro penso che debba impararmi i SmartPtr pensando alle eccezioni.
Si va a perdere efficenza con quest’ultimi essendo un tipo più complesso vero?
 
345
111
Hardware Utente
Sistema Operativo
Windows 10
#15
Ecco un grafico preso da qui -> http://www.modernescpp.com/index.php/memory-and-performance-overhead-of-smart-pointer


Come puoi vedere con le ottimizzazioni attive new e unique_ptr sono praticamente identici (con la differenza che unique è molto piu sicuro). shared_ptr ha un pò di overhead perchè deve tenere il "refCount" di tutti gli altri shared_ptr con i quali condivide la risorsa ( questo refCount aumenta di 1 per ogni nuovo shared_ptr che "guarda" quella stessa risorsa e diminuisce quando uno di questi shared_ptr viene distrutto, e quando questo refCount raggiunge 0 allora delete viene chiamato sul raw pointer gestito dall'ultimo shared_ptr rimasto. Un pò come se vai ad un party e l'ultimo che se ne và ha il compito di spegnere le luci e chiudere a chiave. L'ho spiegato male però quindi ti consiglio di cercarlo online :D )