Array di Parametri nel C++

_Achille

Utente Èlite
3,067
725
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
HDD
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
GPU
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
PSU
RM550X
Case
NZXT S340
Periferiche
Anne Pro 2, Razer Abyssus
OS
Windows 10 Pro
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
 

DispatchCode

Moderatore
Staff Forum
Utente Èlite
2,208
1,845
CPU
Intel I9-10900KF 3.75GHz 10x 125W
Dissipatore
Gigabyte Aorus Waterforce X360 ARGB
Scheda Madre
Asus 1200 TUF Z590-Plus Gaming ATX DDR4
HDD
1TB NVMe PCI 3.0 x4, 1TB 7200rpm 64MB SATA3
RAM
DDR4 32GB 3600MHz CL18 ARGB
GPU
Nvidia RTX 3080 10GB DDR6
Audio
Integrata 7.1 HD audio
Monitor
LG 34GN850
PSU
Gigabyte P850PM
Case
Phanteks Enthoo Evolv X ARGB
Periferiche
MSI Vigor GK30, mouse Logitech
Net
FTTH Aruba, 1Gb (effettivi: ~950Mb / ~480Mb)
OS
Windows 10 64bit / OpenSUSE Tumbleweed

_Achille

Utente Èlite
3,067
725
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
HDD
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
GPU
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
PSU
RM550X
Case
NZXT S340
Periferiche
Anne Pro 2, Razer Abyssus
OS
Windows 10 Pro
Ciao, credo che questa discussioni riguardi proprio ciò che stai cercando: https://stackoverflow.com/questions/2433295/how-does-printf-handle-its-arguments
Grazie mille questo è proprio quello che le funzioni standard del C utilizzano
Fantastico! Esiste quindi qualche implementazione del C++. Questa però pare più complessa
 

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
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
Reazioni: _Achille

_Achille

Utente Èlite
3,067
725
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
HDD
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
GPU
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
PSU
RM550X
Case
NZXT S340
Periferiche
Anne Pro 2, Razer Abyssus
OS
Windows 10 Pro
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:

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
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 Èlite
3,067
725
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
HDD
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
GPU
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
PSU
RM550X
Case
NZXT S340
Periferiche
Anne Pro 2, Razer Abyssus
OS
Windows 10 Pro
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).
 

Marcus Aseth

Utente Attivo
404
138
OS
Windows 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
 
  • Mi piace
Reazioni: _Achille

_Achille

Utente Èlite
3,067
725
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
HDD
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
GPU
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
PSU
RM550X
Case
NZXT S340
Periferiche
Anne Pro 2, Razer Abyssus
OS
Windows 10 Pro
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:
 

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
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
Reazioni: _Achille

_Achille

Utente Èlite
3,067
725
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
HDD
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
GPU
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
PSU
RM550X
Case
NZXT S340
Periferiche
Anne Pro 2, Razer Abyssus
OS
Windows 10 Pro
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:
 

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
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 Èlite
3,067
725
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
HDD
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
GPU
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
PSU
RM550X
Case
NZXT S340
Periferiche
Anne Pro 2, Razer Abyssus
OS
Windows 10 Pro
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?
 

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
Ecco un grafico preso da qui -> http://www.modernescpp.com/index.php/memory-and-performance-overhead-of-smart-pointer
comparisonEng.png


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 )
 

Entra

oppure Accedi utilizzando
Discord Ufficiale Entra ora!

Discussioni Simili