¿Por qué las bibliotecas y los frameworks de C ++ nunca usan punteros inteligentes?

156

Leí en algunos artículos que los punteros en bruto casi nunca deberían usarse. En su lugar, siempre deben estar envueltos dentro de punteros inteligentes, ya sean punteros de alcance o compartidos.

Sin embargo, noté que marcos como Qt, wxWidgets y bibliotecas como Boost nunca regresan ni esperan punteros inteligentes, como si no los estuvieran usando en absoluto. En cambio, regresan o esperan indicadores crudos. ¿Hay alguna razón para eso? ¿Debo mantenerme alejado de los punteros inteligentes cuando escribo una API pública y por qué?

Solo me pregunto por qué se recomiendan los punteros inteligentes cuando muchos proyectos importantes parecen evitarlos.

Laurent
fuente
22
Todas esas bibliotecas que acabas de nombrar comenzaron hace muchos años. Los punteros inteligentes solo se volvieron verdaderamente estándar en C ++ 11.
chrisaycock
22
Los punteros inteligentes tienen una sobrecarga (recuento de referencias, etc.), que puede ser crítica, en sistemas integrados / en tiempo real, por ejemplo. En mi humilde opinión, los punteros inteligentes son para programadores perezosos. También muchas API van por el mínimo común denominador. ¡Siento las llamas lamiendo mis pies mientras escribo!
Ed Heal
93
@EdHeal: La razón por la que puedes sentir llamas lamiendo tus pies es porque estás completamente equivocado en todos los aspectos. Por ejemplo, ¿qué gastos generales hay unique_ptr? Ninguno en absoluto. ¿Qt / WxWidgets están dirigidos a sistemas integrados o en tiempo real? No, están destinados a Windows / Mac / Unix en un escritorio, como máximo. Los punteros inteligentes son para programadores que quieren hacerlo bien.
Cachorro
24
Realmente, los teléfonos móviles ejecutan Java.
R. Martinho Fernandes
12
¿Los punteros inteligentes solo son realmente estándar en C ++ 11? ¿¿¿Qué??? Estas cosas se han usado por más de 20 años.
Kaz

Respuestas:

124

Además del hecho de que muchas bibliotecas se escribieron antes del advenimiento de los punteros inteligentes estándar, la razón más importante es probablemente la falta de una interfaz binaria de aplicaciones (ABI) C ++ estándar.

Si está escribiendo una biblioteca de solo encabezado, puede pasar punteros inteligentes y contenedores estándar al contenido de su corazón. Su fuente está disponible para su biblioteca en el momento de la compilación, por lo que confía solo en la estabilidad de sus interfaces, no en sus implementaciones.

Pero debido a la falta de ABI estándar, generalmente no puede pasar estos objetos de forma segura a través de los límites del módulo. Un GCC shared_ptres probablemente diferente de un MSVC shared_ptr, que también puede diferir de un Intel shared_ptr. Incluso con el mismo compilador, no se garantiza que estas clases sean binarias compatibles entre versiones.

La conclusión es que si desea distribuir una versión preconstruida de su biblioteca, necesita un ABI estándar en el que confiar. C no tiene uno, pero los proveedores de compiladores son muy buenos con respecto a la interoperabilidad entre las bibliotecas de C para una plataforma determinada; existen estándares de facto.

La situación no es tan buena para C ++. Los compiladores individuales pueden manejar la interoperación entre sus propios binarios, por lo que tiene la opción de distribuir una versión para cada compilador compatible, a menudo GCC y MSVC. Pero a la luz de esto, la mayoría de las bibliotecas solo exportan una interfaz C, y eso significa punteros sin formato.

Sin embargo, el código que no es de biblioteca generalmente prefiere punteros inteligentes en lugar de sin formato.

Jon Purdy
fuente
17
Estoy de acuerdo con usted, incluso pasar un std :: string puede ser una molestia. Esto dice mucho sobre C ++ como un "gran lenguaje para bibliotecas".
Ha11owed
8
La conclusión es más como: si desea distribuir una versión previa a la compilación, debe hacerlo para cada compilador que desee admitir.
josefx
66
@josefx: Sí, esto es triste pero cierto, la única alternativa es COM o una interfaz C sin formato. Desearía que la comunidad de C ++ comenzara a preocuparse por este tipo de problemas. Quiero decir que no es que C ++ sea un nuevo lenguaje de hace 2 años.
Robot Mess
3
Voté en contra porque esto está mal. Los problemas de ABI son más que manejables en la mayoría de los casos. Aunque apenas es fácil de usar, ABI tampoco es insuperable.
Puppy
44
@NathanAdams: Sin duda, este software es impresionante y útil. Pero trata el síntoma de problemas más profundos: la semántica C ++ de la vida y la propiedad están en algún lugar entre empobrecido e inexistente. Esos errores del montón no habrían surgido si el idioma no los permitiera. Entonces, los punteros inteligentes no son una panacea, son un intento de recuperar algunas de las pérdidas incurridas al usar C ++ en primer lugar.
Jon Purdy
40

Puede haber muchas razones. Para enumerar algunos de ellos:

  1. Los punteros inteligentes se convirtieron en parte del estándar recientemente. Hasta entonces eran parte de otras bibliotecas.
  2. Su uso principal es evitar pérdidas de memoria; muchas bibliotecas no tienen su propia administración de memoria; Generalmente proporcionan utilidades y API
  3. Se implementan como contenedor, ya que en realidad son objetos y no punteros. Que tiene un costo adicional de tiempo / espacio, en comparación con los punteros sin procesar; Los usuarios de las bibliotecas pueden no querer tener tales gastos generales

Editar : El uso de punteros inteligentes es una elección completamente del desarrollador. Depende de varios factores.

  1. En los sistemas críticos de rendimiento, es posible que no desee utilizar punteros inteligentes que generan gastos generales

  2. El proyecto que necesita la compatibilidad con versiones anteriores, es posible que no desee utilizar los punteros inteligentes que tienen características específicas de C ++ 11

Edit2 Hay una cadena de varios votos negativos en el lapso de 24 horas debido al pasaje a continuación. No entiendo por qué se rechaza la respuesta, aunque a continuación se muestra solo una sugerencia adicional y no una respuesta.
Sin embargo, C ++ siempre te facilita tener las opciones abiertas. :) p.ej

template<typename T>
struct Pointer {
#ifdef <Cpp11>
  typedef std::unique_ptr<T> type;
#else
  typedef T* type;
#endif
};

Y en tu código úsalo como:

Pointer<int>::type p;

Para aquellos que dicen que un puntero inteligente y un puntero sin formato son diferentes, estoy de acuerdo con eso. El código anterior era solo una idea en la que uno puede escribir un código que sea intercambiable solo con un #define, esto no es una obligación ;

Por ejemplo, T*tiene que eliminarse explícitamente, pero un puntero inteligente no. Podemos tener una plantilla Destroy()para manejar eso.

template<typename T>
void Destroy (T* p)
{
  delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
  // do nothing
}

y úsalo como:

Destroy(p);

Del mismo modo, para un puntero sin formato podemos copiarlo directamente y para un puntero inteligente podemos usar una operación especial.

Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));

Donde Assign()es como:

template<typename T>
T* Assign (T *p)
{
  return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
  // use move sematics or whateve appropriate
}
iammilind
fuente
14
En 3. Algunos punteros inteligentes tienen costos adicionales de tiempo / espacio, otros no, incluido std::auto_ptrque ha sido parte del estándar durante mucho tiempo (y tenga en cuenta que me gusta std::auto_ptrcomo el tipo de retorno para funciones que crean objetos, incluso si es casi inútil en cualquier otro lugar). En C ++ 11 std::unique_ptrno tiene costos adicionales sobre un puntero simple.
David Rodríguez - dribeas
44
Exactamente ... hay una buena simetría en la aparición unique_ptry desaparición de auto_ptr, el código dirigido a C ++ 03 debería usar el último, mientras que el código dirigido a C ++ 11 puede usar el primero. Los punteros inteligentes no lo son shared_ptr, hay muchos estándares y ninguno estándar, incluidas las propuestas al estándar que fueron rechazadas comomanaged_ptr
David Rodríguez - dribeas
2
@iammilind, esos son puntos interesantes, pero lo curioso es que si terminamos usando punteros inteligentes, como aparentemente muchos recomendarían, terminamos creando código incompatible con las bibliotecas principales. Por supuesto, podemos envolver / desenvolver los punteros inteligentes según sea necesario, pero parece una gran molestia y crearía un código inconsistente (a veces tratamos con punteros inteligentes, a veces no).
Laurent
77
La afirmación de que los punteros inteligentes tienen un "costo adicional de tiempo / espacio" es algo engañosa; todos los punteros inteligentes excepto unique_ptrincurren en costos de tiempo de ejecución, pero unique_ptres, con mucho, el que se usa más comúnmente. El ejemplo de código que usted proporciona también es engañoso, pues unique_ptr, y T*son completamente diferentes conceptos. El hecho de que se refiera a ambos como typeda la impresión de que pueden intercambiarse entre sí.
void-puntero el
12
No se puede escribir así, estos tipos no son equivalentes de ninguna manera. Escribir typedefs como este es pedir problemas.
Alex B
35

Hay dos problemas con los punteros inteligentes (pre C ++ 11):

  • no estándares, por lo que cada biblioteca tiende a reinventar la suya (síndromes de NIH y problemas de dependencias)
  • costo potencial

El puntero inteligente predeterminado , en el sentido de que es gratuito, es unique_ptr. Desafortunadamente, requiere semántica de movimiento C ++ 11, que solo apareció recientemente. Todos los demás punteros inteligentes tienen un costo ( shared_ptr, intrusive_ptr) o tienen una semántica menos que ideal ( auto_ptr).

Con C ++ 11 a la vuelta de la esquina, trayendo un std::unique_ptr, uno estaría tentado a pensar que finalmente se terminó ... No soy tan optimista.

Solo unos pocos compiladores importantes implementan la mayoría de C ++ 11, y solo en sus versiones recientes. Podemos esperar que las principales bibliotecas, como QT y Boost, estén dispuestas a mantener la compatibilidad con C ++ 03 por un tiempo, lo que impide de alguna manera la adopción generalizada de los nuevos y brillantes punteros inteligentes.

Matthieu M.
fuente
12

No debe mantenerse alejado de los punteros inteligentes, tienen su uso especialmente en aplicaciones donde tiene que pasar un objeto.

Las bibliotecas tienden a devolver un valor o completar un objeto. Por lo general, no tienen objetos que deban usarse en muchos lugares, por lo que no es necesario que usen punteros inteligentes (al menos no en su interfaz, pueden usarlos internamente).

Podría tomar como ejemplo una biblioteca en la que hemos estado trabajando, donde después de unos meses de desarrollo me di cuenta de que solo usamos punteros y punteros inteligentes en algunas clases (3-5% de todas las clases).

Pasar variables por referencia fue suficiente en la mayoría de los lugares, usamos punteros inteligentes cada vez que teníamos un objeto que podía ser nulo, y punteros sin procesar cuando una biblioteca que usábamos nos obligaba a hacerlo.

Editar (no puedo comentar debido a mi reputación): pasar variables por referencia es muy flexible: si desea que el objeto sea de solo lectura, puede usar una referencia constante (aún puede hacer algunos cambios desagradables para poder escribir el objeto) ) pero obtienes la máxima protección posible (es lo mismo con los punteros inteligentes). Pero sí estoy de acuerdo en que es mucho mejor devolver el objeto.

Robot Mess
fuente
No estoy en desacuerdo con usted exactamente, pero señalaré que hay una escuela de pensamiento que en la mayoría de los casos desprecia el paso de referencias variables . Confieso que me adhiero a esa escuela. Prefiero funciones para no modificar sus argumentos. En cualquier caso, hasta donde yo sé, las referencias variables de C ++ no hacen nada para evitar el mal manejo de los objetos a los que se refieren, que es lo que intentan hacer los punteros inteligentes.
THB
2
tienes constante para eso (parece que puedo comentar: D).
Robot Mess
9

Qt reinventó sin sentido muchas partes de la biblioteca estándar en un intento de convertirse en Java. Creo que ahora tiene sus propios punteros inteligentes, pero en general, no es un pináculo de diseño. wxWidgets, hasta donde yo sé, fue diseñado mucho antes de que se escribieran los punteros inteligentes utilizables.

En cuanto a Boost, espero que utilicen punteros inteligentes siempre que sea apropiado. Puede que tenga que ser más específico.

Además, no olvide que existen punteros inteligentes para imponer la propiedad. Si la API no tiene semántica de propiedad, ¿por qué usar un puntero inteligente?

Perrito
fuente
19
Qt se escribió antes de que gran parte de la funcionalidad estuviera suficientemente extendida en las plataformas que quería usar. Ha tenido punteros inteligentes durante mucho tiempo y los usa para compartir los recursos de manera implícita en casi todas las clases Q *.
rubenvb
66
Cada biblioteca GUI reinventa innecesariamente la rueda. Incluso cadenas, Qt tiene QString, wxWidgets tiene wxString, MFC tiene el nombre horrible CString. ¿No es un UTF-8 std::stringlo suficientemente bueno para el 99% de las tareas de GUI?
Inverso
10
@Inverse QString se creó cuando std :: string no estaba disponible.
MrFox
Compruebe cuándo se creó qt y qué punteros inteligentes estaban disponibles en ese momento.
Dainius
3

Buena pregunta. No conozco los artículos específicos a los que se refiere, pero he leído cosas similares de vez en cuando. Mi sospecha es que los escritores de tales artículos tienden a albergar un sesgo en contra de la programación estilo C ++. Si el escritor programa en C ++ solo cuando debe hacerlo, luego regresa a Java o similar tan pronto como pueda, entonces realmente no comparte la mentalidad de C ++.

Uno sospecha que algunos o la mayoría de los mismos escritores prefieren los administradores de memoria recolectores de basura. No lo hago, pero pienso de manera diferente que ellos.

Los punteros inteligentes son geniales, pero tienen que mantener el recuento de referencias. El mantenimiento de los recuentos de referencia conlleva costos, a menudo costos modestos, pero no obstante, en el tiempo de ejecución. No hay nada de malo en ahorrar estos costos mediante el uso de punteros vacíos, especialmente si los punteros son administrados por destructores.

Una de las cosas excelentes de C ++ es su compatibilidad con la programación de sistemas integrados. El uso de punteros desnudos es parte de eso.

Actualización: Un comentarista ha observado correctamente que el nuevo C ++ unique_ptr(disponible desde TR1) no cuenta referencias. El comentarista también tiene una definición diferente de "puntero inteligente" que tengo en mente. Puede tener razón sobre la definición.

Actualización adicional: el hilo de comentarios a continuación es esclarecedor. Todo es lectura recomendada.

thb
fuente
2
Para empezar, la programación de sistemas embebidos es una vasta minoría de toda la programación y es bastante irrelevante. C ++ es un lenguaje de propósito general. En segundo lugar, shared_ptrmantiene un recuento de referencia. Hay muchos otros tipos de punteros inteligentes que no mantienen ningún recuento de referencia. Finalmente, las bibliotecas mencionadas están destinadas a plataformas que tienen muchos recursos de sobra. No es que fuera el votante, pero todo lo que digo es que tu publicación está llena de errores.
Cachorro
2
@thb - Estoy de acuerdo con tu sentimiento. DeadMG: intenta vivir sin sistemas integrados. Sí, algunos punteros inteligentes no tienen una sobrecarga, pero otros sí. El OP menciona bibliotecas. Boost, por ejemplo, tiene partes que utilizan los sistemas integrados, pero los punteros inteligentes pueden ser inapropiados para ciertas aplicaciones.
Ed Heal
2
@EdHeal: ¡No vivir sin sistemas integrados! = La programación para ellos no es una minoría pequeña, irrelevante. Los punteros inteligentes son apropiados para cada situación en la que necesita administrar la vida útil de un recurso.
Cachorro
44
shared_ptrNo tiene gastos generales. Solo tiene gastos generales si no necesita una semántica de propiedad compartida segura para subprocesos, que es lo que proporciona.
R. Martinho Fernandes
1
No, shared_ptr tiene una sobrecarga significativa sobre el mínimo necesario para la semántica de propiedad compartida segura para subprocesos; específicamente, asigna un bloque de almacenamiento dinámico separado del objeto real que está compartiendo, con el único propósito de almacenar el recuento. intrusive_ptr es más eficiente, pero (como shared_ptr) también supone que cada puntero al objeto será un intrusive_ptr. Puede obtener una sobrecarga aún más baja que intrusive_ptr con un puntero compartido de recuento de referencias personalizado, como lo hago en mi aplicación, y luego usar T * siempre que pueda garantizar que al menos un puntero inteligente supere el valor T *.
Qwertie
2

También hay otros tipos de punteros inteligentes. Es posible que desee un puntero inteligente especializado para algo como la replicación de la red (uno que detecta si se accede y envía modificaciones al servidor o algo así), mantiene un historial de cambios, marca el hecho de que se accedió para que se pueda investigar cuando guarda datos en el disco, etc. No estoy seguro de si hacer eso en el puntero es la mejor solución, pero el uso de los tipos de puntero inteligente integrados en las bibliotecas podría provocar que las personas se enganchen en ellos y pierdan la flexibilidad.

Las personas pueden tener todo tipo de requisitos y soluciones de administración de memoria diferentes más allá de los punteros inteligentes. Es posible que desee administrar la memoria yo mismo, podría estar asignando espacio para cosas en un grupo de memoria para que se asigne por adelantado y no en tiempo de ejecución (útil para juegos). Podría estar usando una implementación de C ++ recolectada de basura (C ++ 11 lo hace posible aunque todavía no existe). O tal vez simplemente no estoy haciendo nada lo suficientemente avanzado como para preocuparme por molestarme con ellos, puedo saber que no voy a olvidar los objetos no inicializados, etc. Tal vez solo confío en mi capacidad para administrar la memoria sin la muleta del puntero.

La integración con C es otro problema también.

Otro problema es que los punteros inteligentes son parte de la STL. C ++ está diseñado para ser utilizable sin el STL.

David C. Bishop
fuente
" Otro problema es que los punteros inteligentes son parte del STL " . No lo son.
curioso
0

También depende del dominio en el que trabajes. Escribo motores de juego para vivir, evitamos el impulso como la peste, en los juegos la sobrecarga del impulso no es aceptable. En nuestro motor central terminamos escribiendo nuestra propia versión de stl (al igual que ea stl).

Si tuviera que escribir una aplicación de formularios, podría considerar el uso de punteros inteligentes; pero una vez que la gestión de la memoria es una segunda naturaleza, no tener un control granular sobre la memoria se vuelve silencioso y molesto.

Davis feo
fuente
3
No existe tal cosa como la "sobrecarga del impulso".
curioso
44
Nunca he compartido shared_ptr ralentizar mi motor de juego a un grado notable. Sin embargo, han acelerado el proceso de producción y depuración. Además, ¿qué quiere decir exactamente con "la sobrecarga del impulso"? Esa es una manta bastante grande para echar.
derpface
@curiousguy: Es la sobrecarga de compilación de todos esos encabezados y vudú macro + plantilla ...
einpoklum