Quando dovrei usare i puntatori grezzi rispetto ai puntatori intelligenti?

Dopo aver letto questa risposta , sembra che sia una buona pratica utilizzare il più ansible i puntatori intelligenti e ridurre al minimo l’utilizzo di puntatori “normali” / grezzi.

È vero?

No, non è vero. Se una funzione ha bisogno di un puntatore e non ha nulla a che fare con la proprietà, allora credo fermamente che un puntatore regolare dovrebbe essere passato per i seguenti motivi:

  • Nessuna proprietà, quindi non sai che tipo di puntatore intelligente passare
  • Se passi un puntatore specifico, come shared_ptr , non sarai in grado di passare, per esempio, scoped_ptr

La regola sarebbe questa: se sai che un’ quadro deve prendere un certo tipo di proprietà dell’object, usa sempre dei puntatori intelligenti – quello che ti dà il tipo di proprietà di cui hai bisogno. Se non esiste la nozione di proprietà, non utilizzare mai i puntatori intelligenti.

Esempio 1:

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

Esempio 2:

 Object* createObject() //bad { return new Object; } some_smart_ptr createObject() //good { return some_smart_ptr(new Object); } 

Usare puntatori intelligenti per gestire la proprietà è la cosa giusta da fare. Viceversa, l’utilizzo di puntatori grezzi laddove la proprietà non è un problema non è sbagliato.

Ecco alcuni usi perfettamente legittimi dei puntatori grezzi (ricorda, si presume sempre che non siano proprietari):

dove competono con i riferimenti

  • argomento che passa; ma i riferimenti non possono essere nulli, quindi sono preferibili
  • come membri della class per indicare associazione piuttosto che composizione; di solito preferibile ai riferimenti perché la semantica dell’assegnazione è più semplice e inoltre un invariante impostato dai costruttori può garantire che non siano 0 per la durata dell’object
  • come maniglia per un object (possibilmente polimorfico) di proprietà altrove; i riferimenti non possono essere nulli quindi di nuovo sono preferibili
  • std::bind usa una convenzione in cui gli argomenti che vengono passati vengono copiati nel functor risultante; tuttavia std::bind(&T::some_member, this, ...) esegue solo una copia del puntatore mentre std::bind(&T::some_member, *this, ...) copia l’object; std::bind(&T::some_member, std::ref(*this), ...) è un’alternativa

dove non competono con i riferimenti

  • come iteratori!
  • argomento passando di parametri opzionali ; qui competono con boost::optional
  • come handle di un object (possibilmente polimorfico) di proprietà altrove, quando non possono essere dichiarati nel sito di inizializzazione; di nuovo, in competizione con boost::optional

Come promemoria, è quasi sempre sbagliato scrivere una funzione (che non è un costruttore, o un membro di una funzione che ad esempio assume la proprietà) che accetta un puntatore intelligente a meno che non lo trasmetta a sua volta a un costruttore (ad es. È corretto per std::async perché semanticamente è vicino ad essere una chiamata al costruttore std::thread ). Se è sincrono, non è necessario il puntatore intelligente.


Per ricapitolare, ecco uno snippet che dimostra alcuni degli usi di cui sopra. Stiamo scrivendo e utilizzando una class che applica un funtore a ogni elemento di uno std::vector mentre scrive un 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()) // 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 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 const& v) : log(&os) , data(&v) {} // now apply_and_log(std::cout, std::vector {}) is invalid in C++0x // && is also acceptable instead of const&& apply_and_log(std::ostream& os, std::vector 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 const&)> callback_type; // optional callback // alternative: boost::optional 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 const* data; }; 

L’uso di puntatori intelligenti è sempre consigliato in quanto documentano chiaramente la proprietà.

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

 template  class ptr // thanks to Martinho for the name suggestion :) { public: ptr(T* p): _p(p) {} template  ptr(U* p): _p(p) {} template  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 

Questa è, in effetti, la versione più semplice di qualsiasi puntatore intelligente che possa esistere: un tipo che documenta che non possiede la risorsa che punta anche lui.

Un’istanza in cui il conteggio dei riferimenti (usato in particolare da shared_ptr) si interromperà quando si crea un ciclo fuori dai puntatori (ad esempio A punta a B, B punta a A o A-> B-> C-> A, o eccetera). In tal caso, nessuno degli oggetti sarà mai automaticamente liberato, poiché tutti mantengono i conteggi dei riferimenti reciproci superiori a zero.

Per questo motivo, ogni volta che creo oggetti che hanno una relazione genitore-figlio (ad esempio un albero di oggetti), userò shared_ptrs negli oggetti parent per tenere i loro oggetti figli, ma se gli oggetti figli hanno bisogno di un puntatore per tornare al loro genitore , Userò un semplice puntatore C / C ++ per quello.

Pochi casi, dove potresti voler usare i puntatori:

  • Puntatori di funzione (ovviamente nessun puntatore intelligente)
  • Definire il proprio puntatore o contenitore intelligente
  • Trattare con la programmazione di basso livello, dove i puntatori grezzi sono cruciali
  • Decadente dagli array grezzi

Penso che sia stata data una risposta un po ‘più approfondita: quale tipo di puntatore uso quando?

Tratto da quel link: “Usa puntatori muti (puntatori grezzi) o riferimenti per riferimenti non proprietari alle risorse e quando sai che la risorsa sopravviverà all’object / ambito di riferimento.” (in grassetto conservato dall’originale)

Il problema è che se stai scrivendo un codice per uso generale non è sempre facile essere certi che l’object sopravviverà al puntatore raw. Considera 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_list) { employee_list.clear(); employee_list.push_back(*p_new_employee); } void main(int argc, char* argv[]) { std::list 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); } 

Con sua grande sorpresa, la funzione replace_current_employees_with() può inavvertitamente far deallocare uno dei suoi parametri prima di averlo finito.

Quindi, anche se a prima vista potrebbe sembrare che la funzione replace_current_employees_with() non abbia bisogno della proprietà dei suoi parametri, ha bisogno di una sorta di difesa contro la possibilità che i suoi parametri vengano insidiosamente deallocati prima di averli terminati. La soluzione più semplice è quella di assumere effettivamente una proprietà (temporanea condivisa) dei parametri, presumibilmente attraverso una shared_ptr .

Ma se davvero non vuoi diventare proprietario, ora c’è un’opzione sicura – e questa è la parte spudoratamente plug della risposta – ” puntatori registrati “. I “puntatori registrati” sono puntatori intelligenti che si comportano come puntatori grezzi, tranne che sono (automaticamente) impostati su null_ptr quando l’object di destinazione viene distrutto e, per impostazione predefinita, genererà un’eccezione se si tenta di accedere a un object che è già stato eliminato .

Si noti inoltre che i puntatori registrati possono essere “disabilitati” (sostituiti automaticamente con la controparte del puntatore raw) con una direttiva in fase di compilazione, che consente loro di essere utilizzati (e di sostenere) in modalità debug / test / beta. Quindi dovresti davvero dover ricorrere ai veri puntatori grezzi piuttosto raramente.

È vero. Non riesco a vedere i vantaggi dei puntatori grezzi rispetto ai puntatori intelligenti, specialmente in un progetto complesso.

Per uso temporaneo e leggero, i puntatori grezzi vanno bene comunque.