versiones guia español descargar actualizar c++ pointers boost smart-pointers

c++ - guia - ¿Cuándo debería usar punteros sin formato sobre punteros inteligentes?



qgis manual (7)

Creo que aquí se dio una respuesta un poco más completa: ¿qué tipo de puntero uso cuando?

Extraído de ese enlace: "Utilice punteros estúpidos (punteros sin formato) o referencias para referencias no propietarias a los recursos y cuando sepa que el recurso sobrevivirá al objeto / alcance de referencia". (negrita conservada del original)

El problema es que si está escribiendo código para uso general, no siempre es fácil estar absolutamente seguro de que el objeto sobrevivirá al puntero sin procesar. Considera este ejemplo:

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); }

Para su sorpresa, la función replace_current_employees_with() puede causar inadvertidamente la desasignación de uno de sus parámetros antes de que termine de usarlo.

Así que aunque al principio parezca que la función replace_current_employees_with() no necesita la propiedad de sus parámetros, necesita algún tipo de defensa contra la posibilidad de que sus parámetros sean desasignados insidiosamente antes de que termine de usarlos. La solución más simple es tomar realmente la propiedad (temporal compartida) de los parámetros, presumiblemente a través de un shared_ptr .

Pero si realmente no quiere apropiarse, ahora existe una opción segura, y esta es la parte del complemento descarado de la respuesta, " punteros registrados ". "punteros registrados" son punteros inteligentes que se comportan como punteros sin procesar, excepto que se establecen (automáticamente) en null_ptr cuando se destruye el objeto de destino, y de forma predeterminada arrojarán una excepción si intenta acceder a un objeto que ya ha sido eliminado .

También tenga en cuenta que los punteros registrados pueden ser "deshabilitados" (reemplazados automáticamente con su homólogo de puntero sin formato) con una directiva en tiempo de compilación, lo que les permite ser utilizados (e incurrir en gastos generales) solo en modos de depuración / prueba / beta. Por lo tanto, realmente debería recurrir a punteros crudos reales con bastante poca frecuencia.

Después de leer esta respuesta , parece que es una buena práctica utilizar punteros inteligentes tanto como sea posible y reducir al mínimo el uso de punteros "normales" / sin formato.

¿Es eso cierto?


El uso de punteros inteligentes para administrar la propiedad es lo correcto. Por el contrario, el uso de punteros crudos siempre que la propiedad no sea un problema no es incorrecto.

Aquí hay algunos usos perfectamente legítimos de punteros crudos (recuerde, siempre se supone que no son propietarios):

donde compiten con referencias

  • discusión pasando; pero las referencias no pueden ser nulas, entonces son preferibles
  • como miembros de la clase para denotar asociación en lugar de composición; generalmente preferible a las referencias porque la semántica de la asignación es más sencilla y, además, una configuración invariante establecida por los constructores puede garantizar que no sean 0 durante la vida útil del objeto
  • como manejador de un objeto (posiblemente polimórfico) que se posee en otro lugar; las referencias no pueden ser nulas así que de nuevo son preferibles
  • std::bind usa una convención donde los argumentos que se pasan se copian en el functor resultante; sin embargo, std::bind(&T::some_member, this, ...) solo hace una copia del puntero mientras que std::bind(&T::some_member, *this, ...) copia el objeto; std::bind(&T::some_member, std::ref(*this), ...) es una alternativa

donde no compiten con referencias

  • como iteradores!
  • argumento que pasa de parámetros opcionales ; aquí compiten con boost::optional<T&>
  • como un identificador para un objeto (posiblemente polimórfico) que se posee en otro lugar, cuando no se pueden declarar en el sitio de inicialización; de nuevo, compitiendo con boost::optional<T&>

Como recordatorio, casi siempre es incorrecto escribir una función (que no sea un constructor o un miembro de una función que, por ejemplo, asuma la propiedad) que acepte un puntero inteligente a menos que, a su vez, lo pase a un constructor (por ejemplo, es correcto para std::async porque semánticamente está cerca de ser una llamada al constructor std::thread ). Si es sincrónico, no es necesario el puntero inteligente.

Para recapitular, aquí hay un fragmento que demuestra varios de los usos anteriores. Estamos escribiendo y usando una clase que aplica un funtor a cada elemento de un std::vector<int> al escribir algunos resultados.

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; };


El uso de punteros inteligentes siempre se recomienda porque documentan claramente la propiedad.

Lo que realmente extrañamos, sin embargo, es un puntero inteligente "en blanco", que no implica ninguna noción de propiedad.

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>

Esta es, de hecho, la versión más simple de cualquier puntero inteligente que pueda existir: un tipo que documenta que no posee el recurso que señala también.


Es verdad. No puedo ver los beneficios de los punteros sin formato sobre punteros inteligentes, especialmente en un proyecto complejo.

Para uso tempory y liviano, los punteros sin procesar están bien.


No, eso no es verdad. Si una función necesita un puntero y no tiene nada que ver con la propiedad, entonces creo firmemente que se debe pasar un puntero normal por las siguientes razones:

  • Sin propiedad, por lo tanto, no sabes qué tipo de puntero inteligente pasar
  • Si pasa un puntero específico, como shared_ptr , entonces no podrá pasar, digamos, scoped_ptr

La regla sería esta: si usted sabe que una entidad debe tomar un cierto tipo de propiedad del objeto, siempre use punteros inteligentes, el que le da el tipo de propiedad que necesita. Si no hay una noción de propiedad, nunca use punteros inteligentes.

Ejemplo 1:

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(); }

Ejemplo2:

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


Pocos casos, donde es posible que desee utilizar punteros:

  • Punteros de función (obviamente, ningún puntero inteligente)
  • Definición de su propio puntero inteligente o contenedor
  • Tratando con la programación de bajo nivel, donde los punteros crudos son cruciales
  • Decaimiento de matrices en bruto

Un ejemplo en el que el recuento de referencias (utilizado por shared_ptr en particular) se romperá es cuando crea un ciclo de los punteros (por ejemplo, A apunta a B, B apunta a A, o A-> B-> C-> A, o etc.) En ese caso, ninguno de los objetos se liberará automáticamente, ya que todos guardan recuentos de referencia entre ellos superiores a cero.

Por esa razón, cada vez que estoy creando objetos que tienen una relación padre-hijo (por ejemplo, un árbol de objetos), usaré shared_ptrs en los objetos padre para contener sus objetos secundarios, pero si los objetos secundarios necesitan un puntero a su padre , Usaré un puntero C / C ++ para eso.