Quando devo usare il raw puntatori oltre puntatori intelligenti?

Dopo la lettura questa risposta, sembra che è consigliabile utilizzare puntatori intelligenti per quanto possibile, e di ridurre l’utilizzo di “normale”/raw puntatori al minimo.

È vero?

  • Solo per la cronaca, molti puntatore intelligente tipi sono altrettanto veloci come materie prime per i puntatori. E ‘ solo che quando si dice “puntatore intelligente”, quasi tutti si sente come “puntatore condiviso”, e condiviso i puntatori sono molto più lento rispetto a raw puntatori. Ma scoped_ptr o unique_ptr non hanno alcun impatto sulle prestazioni. Così “voglio prestazioni” non è davvero una valida scusa per evitare i puntatori intelligenti. Solo per evitare shared_ptr specifcially

 

7 Replies
  1. 80

    No, non è vero. Se una funzione ha bisogno di un puntatore e non ha nulla a che fare con la proprietà, quindi credo fortemente che un normale puntatore deve essere passato per i seguenti motivi:

    • Alcun diritto di proprietà, quindi non so che tipo di un puntatore intelligente per passare
    • Se si passa un puntatore di specifiche, come shared_ptr, quindi non sarà in grado di passare, per dire, scoped_ptr

    La regola sarebbe questa, se si sa che un ente deve prendere un certo tipo di proprietà di un oggetto, sempre utilizzare i puntatori intelligenti, quello che offre la natura della proprietà di cui hai bisogno. Se non vi è alcuna nozione di proprietà, mai utilizzare i puntatori intelligenti.

    Esempio1:

    void PrintObject(shared_ptr<const Object> po) //bad
    {
        if(po)
          po->Print();
        else
          log_error();
    }
    
    void PrintObject(const Object* po) //good
    {
        if(po)
          po->Print();
        else
          log_error();
    }

    Esempio2:

    Object* createObject() //bad
    {
        return new Object;
    }
    
    some_smart_ptr<Object> createObject() //good
    {
       return some_smart_ptr<Object>(new Object);
    }
    • Un’alternativa in Esempio1 è quello di passare un riferimento non viene trasferita la proprietà – come nel vecchio auto_ptr.
    • Mi piace la nota in merito alla proprietà (che è quello che i puntatori intelligenti sono tutti circa). Tuttavia, si può prendere in considerazione che PrintObject dovrebbe prendere un const& invece di un const*. In quel modo, hai la garanzia di non essere in grado di trasferire la proprietà, quindi non c’è nessuna questione se PrintObject di prendere possesso di esso o non.
    • il passaggio di un auto_ptr sarebbe semplicemente sbagliato, sarebbe distruggere l’oggetto dopo la funzione di completamento
    • Anzi, vorrei modificare la mia risposta per rendere il puntatore necessario 🙂
    • Sì, ma per evitare il trasferimento di proprietà si passa un riferimento. Non ha senso per me quando l’ho scritto ma la riflessione è meno chiaro.
    • Penso che si dovrebbe dire “void PrintObject(Oggetto const* const po)”, altrimenti non si garantisce che il puntatore non cambia per un altro indirizzo all’interno della funzione.
    • Non mi piace di livello superiore consts sul mio parametri di una funzione. Anche se il puntatore non cambia di punto a un altro indirizzo originale puntatore passato rimarrebbe inalterato
    • Sono d’accordo che il parametro deve essere passato per riferimento, se possibile. Vorrei suggerire un puntatore raw essere utilizzato solo se la funzione non deve assumere la proprietà e l’oggetto è opzionale in modo che il puntatore può avere bisogno di essere NULL.
    • Nota che questa risposta vale anche per i membri della classe, e non solo per i parametri della funzione. Se il membro della classe implica la proprietà, i puntatori intelligenti essere una soluzione adeguata; se il membro della classe è rigorosamente per la navigazione, raw puntatori sono più appropriate. (Si noti che se si utilizza un paradigma OO, l’unico “proprietario” è spesso la classe stessa, e non c’è modo this può essere fatto per essere un puntatore intelligente.)
    • Sono completamente d’accordo con te, tranne la parte in cui dici che non c’è modo this può essere fatto per essere un puntatore intelligente. È possibile, per mezzo di boost::make_shared_from_this
    • Il tipo di this è specificato dalla norma, e non è una smart tipo di puntatore. boost::make_shared_from_this fornisce un meccanismo di sicurezza per la creazione di un boost::shared_ptr da this, ma non cambia il tipo di this stesso. (IIRC, è l’oggetto di condivisione in proprietà.) Ci sono anche invasivo puntatori (tra cui in boost) che permettono in modo sicuro la creazione di puntatori intelligenti da this.
    • Wow. Non sapevo che l’originale puntatore rimarrebbe inalterato, ho appena provato (solo aggiunto “po = 0;” in funzione). Mi lascia perplesso. Cercherò di saperne di più su questo.
    • Beh, certo che non si passa il puntatore del VALORE (cioè la copia di esso). Il comportamento che ci aspettiamo se è passato un riferimento a un puntatore come in const Object*& po
    • Un buon punto. Sono d’accordo 🙂
    • per evitare che il puntatore di cambiare a un altro indirizzo Object *const, non const Object* .
    • Il fatto è che ha pensato che senza di livello superiore const il parametro passato potrebbe essere cambiata 🙂
    • Meglio : void PrintObject(const Object &po)
    • non se un oggetto null può essere superato
    • beh, sì. 🙂 Personalmente preferisco evitare le funzioni che possono assumere valori Null in C++, ma forse solo a me…
    • Se un oggetto null può essere superato, si potrebbe anche usare boost::optional, come in void PrintObject(boost::optional<Object const&> var = boost::optional<Object const&>());

  2. 14

    Utilizzando i puntatori intelligenti per gestire la proprietà è la cosa giusta da fare.
    Al contrario, utilizzo di materie prime puntatori ovunque la proprietà è non un problema è non sbagliato.

    Qui ci sono alcune perfettamente legittimo utilizzo di materie prime di puntatori (ricordate, si parte sempre dal presupposto che non sono in possesso):

    in cui si trovano a competere con i riferimenti

    • argomento di passaggio, ma i riferimenti non possono essere null, quindi sono preferibili
    • come i membri della classe a indicare associazione, piuttosto che di composizione, di solito preferibile riferimenti perché la semantica di assegnazione sono di più semplice e, in aggiunta, un’invariante di impostare con i costruttori possono garantire che essi non sono 0 per la vita dell’oggetto
    • come una maniglia per un (forse polimorfici) oggetto posseduto da qualche altra parte; riferimenti non possono essere null, quindi di nuovo sono preferibili
    • std::bind utilizza una convenzione in cui argomenti che vengono passati sono stati copiati nella risultante funtore; tuttavia std::bind(&T::some_member, this, ...) fa solo una copia del puntatore mentre std::bind(&T::some_member, *this, ...) copia l’oggetto; std::bind(&T::some_member, std::ref(*this), ...) è un’alternativa

    dove fanno non competere con riferimenti

    • come iteratori!
    • argomento passaggio di opzionale parametri; qui si trovano a competere con boost::optional<T&>
    • come una maniglia per un (forse polimorfici) oggetto posseduto da qualche altra parte, quando non possono essere dichiarati al sito di inizializzazione; di nuovo, in competizione con boost::optional<T&>

    Come un promemoria, è quasi sempre sbagliato scrivere una funzione (che non è un costruttore o una funzione membro che, ad esempio, assume la proprietà), che accetta un puntatore intelligente, a meno che essa a sua volta la passa ad un costruttore (ad esempio, è corretto per std::async perché semanticamente è vicino ad essere una chiamata al std::thread costruttore). Se è sincrona, non c’è bisogno per la smart pointer.


    Per ricapitolare, ecco un frammento che illustra alcuni degli usi di cui sopra. Stiamo scrivendo, e l’utilizzo di una classe che si applica a un funtore ad ogni elemento di un std::vector<int> durante la scrittura di un po ‘ di output.

    class apply_and_log {
    public:
        //C++03 exception: it's acceptable to pass by pointer to const
        //to avoid apply_and_log(std::cout, std::vector<int>())
        //notice that our pointer would be left dangling after call to constructor
        //this still adds a requirement on the caller that v != 0 or that we throw on 0
        apply_and_log(std::ostream& os, std::vector<int> const* v)
            : log(&os)
            , data(v)
        {}
    
        //C++0x alternative
        //also usable for C++03 with requirement on v
        apply_and_log(std::ostream& os, std::vector<int> const& v)
            : log(&os)
            , data(&v)
        {}
        //now apply_and_log(std::cout, std::vector<int> {}) is invalid in C++0x
        //&& is also acceptable instead of const&&
        apply_and_log(std::ostream& os, std::vector<int> const&&) = delete;
    
        //Notice that without effort copy (also move), assignment and destruction
        //are correct.
        //Class invariants: member pointers are never 0.
        //Requirements on construction: the passed stream and vector must outlive *this
    
        typedef std::function<void(std::vector<int> const&)> callback_type;
    
        //optional callback
        //alternative: boost::optional<callback_type&>
        void
        do_work(callback_type* callback)
        {
            //for convenience
            auto& v = *data;
    
            //using raw pointers as iterators
            int* begin = &v[0];
            int* end = begin + v.size();
            //...
    
            if(callback) {
                callback(v);
            }
        }
    
    private:
        //association: we use a pointer
        //notice that the type is polymorphic and non-copyable,
        //so composition is not a reasonable option
        std::ostream* log;
    
        //association: we use a pointer to const
        //contrived example for the constructors
        std::vector<int> const* data;
    };
  3. 6

    L’uso di puntatori intelligenti è sempre consigliato perché chiaramente, di un documento di proprietà.

    Quello che ci manca davvero, però, è un “vuoto” puntatore intelligente, uno che non implica alcuna nozione di proprietà.

    template <typename T>
    class ptr //thanks to Martinho for the name suggestion :)
    {
    public:
      ptr(T* p): _p(p) {}
      template <typename U> ptr(U* p): _p(p) {}
      template <typename SP> ptr(SP const& sp): _p(sp.get()) {}
    
      T& operator*() const { assert(_p); return *_p; }
      T* operator->() const { assert(_p); return _p; }
    
    private:
      T* _p;
    }; //class ptr<T>

    Questo, infatti, è la versione più semplice di qualsiasi puntatore intelligente che possa esistere: un tipo di documenti che non possiede la risorsa si punti troppo.

    • Non vedo come questo è una “smart” puntatore. Si tratta di un involucro intorno ad un puntatore raw che né azioni di proprietà, né in grado di rilevare quando la punta all’oggetto è stato eliminato. Che vantaggio offre più di un semplice puntatore raw?
    • funzionalmente ? nessuno. Dal punto di vista semantico, tuttavia, chiarisce che la proprietà è stata considerata, e che è stato deciso che la variabile sarebbe non hanno la proprietà. Quando vedi un metodo void foo(Bar*) è sempre ambiguo se o non il metodo assume la proprietà del puntatore o no, se vedi void foo(client_ptr<Bar>) poi è sa che non prende la proprietà-anche se mi sarebbe interessato un nome migliore 🙂
    • Oh, vedo. Tecnica interessante, non avrei mai pensato di usare una classe in questo modo.
    • Nomi divertenti: not_so_smart_ptr<T>, stupid_ptr<T>, sep_ptr<T>. Su una nota seria, penso che solo ptr<T> è ok.
    • perché non !
    • Suona più come un pointless_ptr<T> per me. Un puntatore intelligente la garanzia che la proprietà segue indicato semantica. Questo non garantisce nulla, semplicemente indica che “qualcuno, da qualche parte, pensato che questo non dovrebbe avere la proprietà”. Esattamente come un puntatore raw fa. Non fa alcuna garanzia la complessità che il programma effettivamente si comportano del genere.
    • Discussione: chat.stackoverflow.com/rooms/10/conversation/dumb-ptr.

  4. 4

    Un’istanza in cui il reference counting (utilizzato da shared_ptr in particolare) si rompe quando si crea un ciclo di indicatori (ad esempio, punti B, B punti per Un, o Una->B>C>A, o, ecc). In quel caso, nessuno degli oggetti potrà mai essere liberata automaticamente, perché sono tutti talmente semplice mantenere aggiornati ogni altri conteggi di riferimento maggiore di zero.

    Per questo motivo, ogni volta che sto creando oggetti che hanno un rapporto genitore-figlio (ad esempio, un albero di oggetti), io uso shared_ptrs nel genitore oggetti di tenere il loro bambino di oggetti, ma se il bambino oggetti bisogno di un puntatore a un genitore, io uso un normale C/C++ puntatore per.

    • Buon punto, ma ancora qual debole puntatori sono, come boost::weak_ptr
    • Ah sì, mi ero dimenticato di circa weak_ptr.
  5. 1

    Pochi casi, dove è possibile che si desidera utilizzare puntatori:

    • Puntatori a funzione (ovviamente non smart pointer)
    • Definizione di smart pointer o un contenitore
    • Trattare con programmazione a basso livello, dove raw puntatori sono cruciali
    • Decadente da raw matrici
    • Anche se è possibile utilizzare contenitori invece di matrici, o smart array. Inoltre, perché la programmazione di basso livello necessitano di raw puntatori (forse ci sono posti, ma non ha specificato su di loro)?
    • C’è qualcosa di simile a un puntatore intelligente per funzioni: boost::function o std::function. Quelli sono dei wrapper che gestiscono la copia, ecc…. per i vari tipi di funzione di oggetti, i puntatori a funzione, etc.
  6. 1

    Penso che un po ‘ più approfondita risposta è stata data qui: Che tipo di puntatore faccio uso quando?

    Tratto dal link: “Utilizzare muto puntatori (raw puntatori o riferimenti per non possedere riferimenti di risorse e quando si sa che il risorsa trascenderà l’oggetto di riferimento /portata”. (in grassetto conserva)

    Il problema è che se si sta scrivendo codice per l’uso in generale, non è sempre facile essere assolutamente certi che l’oggetto trascenderà il puntatore raw. Si consideri questo esempio:

    struct employee_t {
        employee_t(const std::string& first_name, const std::string& last_name) : m_first_name(first_name), m_last_name(last_name) {}
        std::string m_first_name;
        std::string m_last_name;
    };
    
    void replace_current_employees_with(const employee_t* p_new_employee, std::list<employee_t>& employee_list) {
        employee_list.clear();
        employee_list.push_back(*p_new_employee);
    }
    
    void main(int argc, char* argv[]) {
        std::list<employee_t> current_employee_list;
        current_employee_list.push_back(employee_t("John", "Smith"));
        current_employee_list.push_back(employee_t("Julie", "Jones"));
        employee_t* p_person_who_convinces_boss_to_rehire_him = &(current_employee_list.front());
    
        replace_current_employees_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list);
    }

    Molto per la sua sorpresa, il replace_current_employees_with() funzione può causare inavvertitamente uno dei suoi parametri per essere rilasciato prima della fine del suo utilizzo.

    Così, anche se a prima vista potrebbe sembrare come il replace_current_employees_with() funzione non ha bisogno di titolarità dei parametri, ha bisogno di qualche tipo di difesa contro la possibilità dei suoi parametri insidiosamente deallocato prima che sia finito il loro uso. La soluzione più semplice è prendere (condivisa temporanea) di proprietà del parametro(s), presumibilmente attraverso un shared_ptr.

    Ma se davvero non si vuole assumere la proprietà, c’è ora un’opzione sicura – e questa è la spina spudorato parte della risposta – “registrati puntatori“. “registrati puntatori” sono puntatori intelligenti che si comportano come i raw puntatori, salvo che essi siano (automaticamente) situato a null_ptr quando l’oggetto di destinazione è distrutta, e per impostazione predefinita, verrà generata un’eccezione se si tenta di accedere a un oggetto che è già stato eliminato.

    Anche la nota che ha registrato i puntatori possono essere “disattivato” (sostituito automaticamente con il loro puntatore raw controparte) con un tempo di compilazione direttiva, consentendo loro di essere utilizzati (e incorrere in testa) in debug/test/beta solo le modalità. Così si dovrebbe davvero resort raw effettivi puntatori molto raramente.

  7. -2

    È vero. Non riesco a vedere i vantaggi del raw puntatori oltre puntatori intelligenti, soprattutto in un progetto complesso.

    Per temporanei e leggero utilizzo, raw puntatori sono belle però.

Lascia un commento