STL e Riallocazione

di il
14 risposte

STL e Riallocazione

Ciao, nell'implementare un algoritmo mi sono ritrovato in una situazione del genere (ovviamente molto semplificata rispetto al problema originale):
vector<T> v;

vector<T*> m[10][10];
man mano che aggiungo elementi a v, vado contemporaneamente anche ad inserire &v.back() in m.

Inizialmente, non prestando particolare attenzione a quello che stavo facendo, sono incappato in un bug che mi ha dato qualche grattacapo: in pratica non avevo tenuto conto delle eventuali riallocazioni di v.
Per il momento ho risolto con v.reserve(n), ma non conoscendo a priori il numero di elementi da aggiungere a v, per andare sul sicuro sono stato costretto a tenermi abbastanza largo con n.
Non avendo molta dimestichezza con i contenitori della STL, volevo chiedere se usare per v una std::list potrebbe essere la scelta giusta?! A tal proposito: la std::list non effettua riallocazioni in NESSUN caso? Una lista concatenata in teoria non ne ha bisogno, ma chiedo per sicurezza.
Se poi avete altre idee riguardo al problema sopra descritto, sono tutto orecchi!


P.S.
Secondo voi la seguente espressione logica può essere ulteriormente semplificata?
A && B || (B || C) && D
(mi riferisco in particolare alla doppia presenza della condizione B)

14 Risposte

  • Re: STL e Riallocazione

    Per l'espressione all'inizio è

    (A && B) ...

    oppure

    A && (B ...
  • Re: STL e Riallocazione

    oregon ha scritto:


    Per l'espressione all'inizio è

    (A && B) ...

    oppure

    A && (B ...
    Ho dato per scontato le regole di precedenza degli operatori, in ogni caso a scanso di equivoci:
    (A && B) || ((B || C) && D)
  • Re: STL e Riallocazione

    Non ho capito che intendi con m. Se volevi fare una matrice dinamica di T*, avrei fatto così:
    vector<vector<T*>>
    Oppure alla vecchia maniera:
    T*** 
    No, la lista non dovrebbe invalidare mai i riferimenti dopo l'inserimento.
    https://en.cppreference.com/w/cpp/containe
    (vedi tabella Iterator invalidation)

    Comunque potresti lavorare in modo un po' più sicuro salvando nella matrice l'indice dell'ultimo elemento inserito, usando un contatore incrementale o "v.size() - 1", anziché i puntatori, così non devi preoccuparti delle riallocazioni.

    Sulla seconda domanda non sono riuscito a trovare ulteriori semplificazioni.
  • Re: STL e Riallocazione

    Neanche io ho trovato modo di semplificare l'espressione logica.
  • Re: STL e Riallocazione

    Alexv ha scritto:


    Non ho capito che intendi con m. Se volevi fare una matrice dinamica di T*, avrei fatto così:
    vector<vector<T*>>
    Oppure alla vecchia maniera:
    T*** 
    Nel problema originale, come già detto, la questione è molto più complessa, ho scelto di inserire i puntatori agli elementi di v in un array bidimensionale di vector m solamente per una questione esemplificativa, ma in ogni caso non penso cambi qualcosa rispetto al problema della riallocazione di v.

    Alexv ha scritto:


    No, la lista non dovrebbe invalidare mai i riferimenti dopo l'inserimento.
    https://en.cppreference.com/w/cpp/containe
    (vedi tabella Iterator invalidation)
    Perfetto, grazie!

    Alexv ha scritto:


    Comunque potresti lavorare in modo un po' più sicuro salvando nella matrice l'indice dell'ultimo elemento inserito, usando un contatore incrementale o "v.size() - 1", anziché i puntatori, così non devi preoccuparti delle riallocazioni.
    In effetti è una buona idea: dal momento che su v effettuo solo inserimenti e nessuna rimozione, con questo approccio dovrei essere in una botte di ferro.

    oregon ha scritto:


    Neanche io ho trovato modo di semplificare l'espressione logica.
    Ok, a questo punto mi sa che il massimo che posso fare è qualcosa del genere:
    bool flag_B = B;
    (A && flag_B) || ((flag_B || C) && D)
    
  • Re: STL e Riallocazione

    Se vuoi usare la lista, tieni conto dei pro e i contro in termini di prestazioni.
    Per andare più sicuro col vettore, puoi usare la funzione membro "at" invece della "parentesi quadre". In questo modo se sfori non rischi un comportamento indefinito, ma viene sollevata subito un'eccezione che se non gestita chiude il programma.

    Sulla funzione logica, avevo pensato a una cosa del genere:
    
    if (B) 
       return  (A || D);
    else
       return (C && D); 
    
    Magari fai un controllo anche tu.

    Un'altra "piccola" ottimizzazione (a seconda se sono variabili booleane o funzioni più complesse) sarebbe quella di mettere:
    - nelle AND, a sinistra l'operando che ritieni statisticamente più probabile essere falso.
    - nelle OR, a sinistra l'operando che ritieni statisticamente più probabile essere vero.
    In questo modo ti risparmi, nella maggior parte dei casi, il controllo del secondo operando.
  • Re: STL e Riallocazione

    Alexv ha scritto:


    Se vuoi usare la lista, tieni conto dei pro e i contro in termini di prestazioni.
    Oltre agli std::vector che ho utilizzato spesso, non conosco bene gli altri contenitori della STL, ma da quanto appena letto mi sembra di capire che std::list sia implementata come una lista doppiamente concatenata che tiene traccia di testa e coda. I pro e contro di vettori e liste ovviamente sono noti, ma nel mio caso, dato che v deve fungere solo da "deposito", le liste sarebbero preferibili perché a differenza dei vettori non sono soggette a riallocazione. In ogni caso penso che alla fine ricorrerò a qualcosa del genere:
    vector<T> v;
    
    vector<unsigned int> m[10][10];
    seguendo la strada da te prima suggerita di tener traccia dell'indice del generico elemento di v invece che del suo indirizzo di memoria.

    Alexv ha scritto:


    Per andare più sicuro col vettore, puoi usare la funzione membro "at" invece della "parentesi quadre". In questo modo se sfori non rischi un comportamento indefinito, ma viene sollevata subito un'eccezione che se non gestita chiude il programma.
    Ero a conoscenza di questa cosa, ma, fin quando ho la certezza di quello che sto facendo, preferisco utilizzare, se non altro per una questione di abitudine, le "parentesi quadre"!

    Alexv ha scritto:


    Sulla funzione logica, avevo pensato a una cosa del genere:
    
    if (B) 
       return  (A || D);
    else
       return (C && D); 
    
    Magari fai un controllo anche tu.
    Così ad occhio sembra funzionare, poi controllo meglio!
    A questo punto poi per una scrittura più compatta si potrebbe anche ricorrere all'operatore ternario:
    (B ? A || D : C && D)

    Alexv ha scritto:


    Un'altra "piccola" ottimizzazione (a seconda se sono variabili booleane o funzioni più complesse) sarebbe quella di mettere:
    - nelle AND, a sinistra l'operando che ritieni statisticamente più probabile essere falso.
    - nelle OR, a sinistra l'operando che ritieni statisticamente più probabile essere vero.
    In questo modo ti risparmi, nella maggior parte dei casi, il controllo del secondo operando.
    Certo, bisogna valutare contemporaneamente sia la probabilità di essere vero/falso dei due operandi sia il loro costo computazionale. In certi casi però più facile a dirsi che a farsi!
  • Re: STL e Riallocazione

    Nippolo ha scritto:


    (B ? A || D : C && D)
    Ottimo per rimorchiare .
    Però se qualche "logico" del forum riuscisse ad eliminare del tutto l'if farebbe bingo.
    Certo, bisogna valutare contemporaneamente sia la probabilità di essere vero/falso dei due operandi sia il loro costo computazionale. In certi casi però più facile a dirsi che a farsi!
    Parlavo in generale, e se si può capirlo "a occhio". Metti ad esempio un for dove una delle condizioni di uscita è i<n, questa è vera solo dopo n tentativi.
  • Re: STL e Riallocazione

    Tornando alla questione principale, provo a ricapitolare le tre opzioni di scelta in mio possesso:
    1)
    
    class A
    {
        static vector<T> v;
        static vector<T*> m[10][10];
        ...
        
        void fun()
        {
            for(unsigned int i = 0; i < 10; ++i)
            {
                for(unsigned int j = 0; j < 10; m[i][j++].resize(0));
            }
            v.resize(0);
            v.reserve(N);
            ... 
            riempimento simultaneo di "v" e "m"
            ...
            for(unsigned int i = 0; i < 10; ++i)
            {
                for(unsigned int j = 0; j < 10; ++j)
                {
                    vector<T*> &r = m[i][j]
                    for(unsigned int k = 0; k < r.size(); ++k)
                    {
                        r[k]-> ...
                        ...
                    }
                }
            }
        }
        
    };
    2)
    
    class A
    {
        static list<T> v;
        static vector<T*> m[10][10];
        ...
        
        void fun()
        {
            for(unsigned int i = 0; i < 10; ++i)
            {
                for(unsigned int j = 0; j < 10; m[i][j++].resize(0));
            }
            v.resize(0);
            ... 
            riempimento simultaneo di "v" e "m"
            ...
            for(unsigned int i = 0; i < 10; ++i)
            {
                for(unsigned int j = 0; j < 10; ++j)
                {
                    vector<T*> &r = m[i][j]
                    for(unsigned int k = 0; k < r.size(); ++k)
                    {
                        r[k]-> ...
                        ...
                    }
                }
            }
        }
        
    };
    3)
    
    class A
    {
        static vector<T> v;
        static vector<unsigneed int> m[10][10];
        ...
        
        void fun()
        {
            for(unsigned int i = 0; i < 10; ++i)
            {
                for(unsigned int j = 0; j < 10; m[i][j++].resize(0));
            }
            v.resize(0);
            ... 
            riempimento simultaneo di "v" e "m"
            ...
            for(unsigned int i = 0; i < 10; ++i)
            {
                for(unsigned int j = 0; j < 10; ++j)
                {
                    vector<unsigned int> &r = m[i][j]
                    for(unsigned int k = 0; k < r.size(); ++k)
                    {
                        v[r[k]]. ...
                        ...
                    }
                }
            }
        }
        
    };
    Ragionando meglio, la scelta forse non è più così scontata:

    - relativamente al caso 2) con le liste, non mi sembra poi molto efficiente dover deallocare e riallocare la memoria ad ogni chiamata di A::fun();

    - il caso 3) con gli indici agli elementi di v va sicuramente bene, ma rispetto al caso 1) richiede un maggior ricorso all'operatore []:
    caso 1): r[k]->
    caso 3): v[r[k]].
    a meno che non si utilizzi un secondo reference:
    T &r2 = v[r[k]];
    L'utilizzo dei reference, oltre ad una questione di efficienza, è dovuto anche al fatto che nel programma originale, essendo i corrispettivi dei contenitori v e m molto più complessi, senza l'utilizzo dei riferimenti verrebbero righe di codice lunghissime e quindi molto poco leggibili;

    - infine per quanto riguarda il caso 1) con i puntatori agli elementi di v, l'unico "contro" è lo spreco di memoria dovuto al ricorso a:
    v.reserve(N);
    Ho provato allora a valutare il suddetto "spreco": utilizzando sizeof(T) ottengo un valore di 184B, mentre ragionando attentamente su N ho dedotto che la massima dimensione di v dovrebbe essere 135, per un totale quindi di 135*184B=~25KB. Ora è vero che di solito utilizzo molti meno posti dei 135 disponibili, ma così su due piedi 25KB non mi sembrano un problema, o sbaglio?
    Già che ci sono poi ne approfitto anche per chiedere un'altra cosa: visto che v è un membro statico della classe A, va bene piazzare il v.reserve(N) in A::fun()? Dal momento che la riallocazione avviene soltanto se N>v.capacity(), sinceramente non ci vedo particolari controindicazioni dal punto di vista pratico, anche se forse dal punto di vista della "buona programmazione" non è il massimo... non so, che dite?
  • Re: STL e Riallocazione

    Ma non hai una dimensione, almeno indicativa, alla quale puoi inizializzare il vettore? Poi se vuoi risparmiare memoria, perché non chiamare direttamente la shrink_to_fit() alla fine?

    L'operatore "quadre" non è per forza meno veloce rispetto a dereferenziare un puntatore.
  • Re: STL e Riallocazione

    Alexv ha scritto:


    Ma non hai una dimensione, almeno indicativa, alla quale puoi inizializzare il vettore?
    Come detto nel precedente post, ho dedotto che la massima dimensione per v dovrebbe essere N=135 e infatti utilizzo v.reserve(N).
    Quando parli di inizializzazione di v intendi sfruttare il costruttore degli std::vector (quello basato su numero di elementi e loro valore) in modo da poter eliminare la chiamata a v.reserve(N)?
    Qualcosa del genere in pratica:
    class A
    {
        static vector<T> v;
        ...
    };
    
    vector<T> A::v(N, "valore");
    In tal caso essendo T una struttura di questo tipo:
    struct T
    {
        vector<class B*> u;
        double d;
        ...
        vector<int> w;
    };
    l'inizializzazione più rapida e concisa sarebbe la seguente:
    vector<T> A::v(N, {{nullptr}});
    o si può fare di meglio?

    Alexv ha scritto:


    Poi se vuoi risparmiare memoria, perché non chiamare direttamente la shrink_to_fit() alla fine?
    Scusa ma nel mio caso, dovendo richiamare A::fun() più volte, non penso abbia molto senso diminuire temporaneamente la capacità di v, visto soprattutto che parliamo di appena (?) 25KB.

    Alexv ha scritto:


    L'operatore "quadre" non è per forza meno veloce rispetto a dereferenziare un puntatore.
    L'ho pensato perché dopotutto l'operatore [] sfrutta sia l'aritmetica dei puntatori che la dereferenziazione.
  • Re: STL e Riallocazione

    Nippolo ha scritto:


    l'inizializzazione più rapida e concisa sarebbe la seguente:
    vector<T> A::v(N, {{nullptr}});
    o si può fare di meglio?
    Come non detto, il secondo argomento del suddetto costruttore è facoltativo, quindi è sufficiente scrivere:
    vector<T> A::v(N);
  • Re: STL e Riallocazione

    Sì intendevo quello, a meno che non serva inizializzare.
  • Re: STL e Riallocazione

    Alla fine ho optato per la versione iniziale coi i puntatori agli elementi di v abbinata col costruttore di cui sopra, chi se ne frega dell'eventuale "spreco di memoria"!

    Grazie per l'aiuto!
Devi accedere o registrarti per scrivere nel forum
14 risposte