2010-04-17 15 views
9

En este insightful article, uno de los programadores de Qt intenta explicar los diferentes tipos de punteros inteligentes que Qt implementa. En un principio, se hace una distinción entre el intercambio de datos y compartir los propios punteros:punteros inteligentes en C++: compartir punteros vs. compartir datos

primer lugar, vamos a dejar una cosa clara: hay una diferencia entre compartir punteros y compartir datos. Cuando comparte punteros, el valor del puntero y su duración está protegido por la clase de puntero inteligente. En otras palabras , el puntero es el invariante. Sin embargo, el objeto al que apunta el puntero está completamente fuera de su control. No sabemos si el objeto es copiable o no, si es asignable o no.

Ahora, el intercambio de datos implica la clase de puntero inteligente sabiendo algo sobre los datos que se comparten. De hecho, el punto es que los datos están compartidos y no nos importa cómo. El hecho de que los punteros se estén utilizando para compartir los datos es irrelevante en este punto. Por ejemplo, a usted no le importa cómo las clases de la herramienta Qt son implícitamente compartidas, ¿verdad? Lo que le importa a usted es que se comparten (reduciendo así el consumo de memoria) y que funcionan como si no lo fueran.

Francamente, simplemente no explico esta explicación. Hubo una declaración de aclaración en los comentarios del artículo, pero no encontré la explicación del autor suficiente.

Si hace comprenda esto, por favor explique. ¿Cuál es esta distinción y cómo se ajustan a esta taxonomía otras clases de punteros compartidos (es decir, de impulso o los nuevos estándares de C++)?

Gracias de antemano

+0

Esta cosa de Qt todavía me confunde :) –

Respuesta

1

En el primer caso, se añaden un nivel de indirección al puntero , de tal manera que el objeto que está representado por el puntero inteligente envuelve el puntero originales. Solo hay un puntero al objeto y el trabajo del empaquetador es realizar un seguimiento de las referencias al puntero original. Un poco muy simplista de código podría tener este aspecto:

template<typename T> 
struct smart_ptr { 
    T *ptr_to_object; 
    int *ptr_to_ref_count; 
}; 

Cuando se copia la estructura, su copia/asignar código tendrá que asegurarse de que el contador de referencias se incrementa (o disminuye si el objeto se destruye), pero el puntero al objeto real envuelto nunca cambiará y puede copiarse superficialmente. Como la estructura es bastante pequeña, es fácil y barata de copiar y "todo" lo que tienes que hacer es manipular el recuento de referencias.

En el segundo caso, me parece más un repositorio de objetos. La parte 'implícitamente compartida' sugiere que puede pedirle al framework un FooWidget haciendo algo como BarFoo.getFooWidget() y aunque parece que el puntero, inteligente o no, que usted recupera es un puntero a un nuevo objeto, en realidad está recibir un puntero a un objeto existente que se mantiene en algún tipo de caché de objetos. En ese sentido, podría ser más parecido a un objeto tipo Singleton que obtienes al invocar un método de fábrica.

Al menos eso es lo que me suena a la distinción, pero podría estar tan fuera de lugar que necesitaría que Google Maps encuentre el camino de regreso.

7

En un comentario más tarde, se aclara el asunto un poco

Este es el punto importante Traté de conseguir a través de la primera sección. Cuando utiliza QSharedPointer, comparte la propiedad del puntero. La clase controla y maneja solo el puntero; cualquier otra cosa (como el acceso a los datos) está fuera de su alcance. Cuando utiliza QSharedDataPointer, está compartiendo los datos. Y esa clase está destinada a compartir implícitamente, por lo que puede dividirse.

Tratando de interpretar que:

Lo que es importante para ver es que "puntero" no significa que el objeto de almacenar la dirección en este caso, sino que significa la ubicación de almacenamiento donde se encuentra el objeto (la dirección sí mismo). Así que estrictamente, creo, tienes que decir que estás compartiendo la dirección. boost::shared_ptr es, por lo tanto, un puntero inteligente que comparte el "puntero". boost::intrusive_ptr u otro puntero inteligente intrusivo parece compartir el puntero también, aunque saber algo sobre el objeto señalado (que tiene un miembro de recuento de referencias o funciones incrementándolo/disminuyéndolo).

Ejemplo: Si alguien comparte una caja negra contigo y no sabe qué hay en la caja negra, es similar a compartir el puntero (que representa la caja), pero no los datos (lo que está dentro del caja). De hecho, ni siquiera se puede saber que lo que está dentro de la caja es compartible (¿y si la caja no contiene nada?). Los indicadores inteligentes están representados por usted y el otro (y no se comparten, por supuesto), pero la dirección es el cuadro y se comparte .

Compartir los datos significa que el puntero inteligente sabe lo suficiente de los datos apuntando a que puede cambiar la dirección apuntada (y esto debe copiar los datos, etc.). Por lo tanto, los punteros ahora pueden apuntan a direcciones diferentes. Como la dirección es diferente, la dirección ya no se comparte. Esto es lo que hace std::string en algunas implementaciones, también:

std::string a("foo"), b(a); 
// a and b may point to the same storage by now. 
std::cout << (void*)a.c_str(), (void*)b.c_str(); 
// but now, since you could modify data, they will 
// be different 
std::cout << (void*)&a[0], (void*)&b[0]; 

compartir datos no significa necesariamente que usted tiene un puntero presentado. Puede usar un std::string por medios puros de a[0] y cout << a; y nunca toque ninguna de las funciones c_str(). Todavía compartir puede ir detrás de la escena. Lo mismo ocurre con muchas clases Qt y clases de otros kits de herramientas de widgets, que se llama compartir implícitamente (o copiar al escribir). Así que creo que se puede resumir así:

  • Compartiendo el puntero: Siempre apuntan a la misma dirección cuando se copia un puntero inteligente, lo que implica que compartimos el valor del puntero.
  • Compartiendo los datos: podemos señalar diferentes direcciones en diferentes momentos. Implicando que sabemos cómo copiar datos de una dirección a la otra.

por lo que tratar de clasificar

  • boost::shared_ptr, boost::intrusive_ptr: permite compartir el puntero, no los datos.
  • QString, QPen, QSharedDataPointer: Comparta los datos que contiene.
  • std::unique_ptr, std::auto_ptr (y también QScopedPointer): Ni compartir el puntero ni los datos.
3

decir que tuvimos esta clase

struct BigArray{ 
    int operator[](size_t i)const{return m_data[i];} 
    int& operator[](size_t i){return m_data[i];} 
private: 
    int m_data[10000000]; 
}; 

Y ahora decir que tuvimos dos casos:

BigArray a; 
a[0]=1;//initializaation etc 
BigArray b=a; 

En este punto queremos que este invariante

assert(a[0]==b[0]); 

la copia por omisión ctor garantiza esta invariante, sin embargo a expensas de copiar profundamente la e ntire objeto. Podemos intentar un aumento de velocidad como esto

struct BigArray{ 
    BigArray():m_data(new int[10000000]){} 
    int operator[](size_t i)const{return (*m_data)[i];} 
    int& operator[](size_t i){return (*m_data)[i];} 
private: 
    shared_ptr<int> m_data; 
}; 

Esto también se reunirá con el invariante, sin hacer la copia en profundidad, por lo que todo está bien hasta ahora. Ahora, utilizando esta nueva implementación que hicimos

b[0]=2; 

Ahora queremos que esto funcione lo mismo que la aserción caso copia profunda (a [0] = b [0]!); Pero falla. Para solucionar esto, necesitamos un ligero cambio:

struct BigArray{ 
     BigArray():m_data(new int[10000000]){} 
     int operator[](size_t i)const{return (*m_data)[i];} 
     int& operator[](size_t i){ 
      if(!m_data.unique()){//"detach" 
      shared_ptr<int> _tmp(new int[10000000]); 
      memcpy(_tmp.get(),m_data.get(),10000000); 
      m_data=_tmp; 
      } 
      return (*m_data)[i]; 
     } 
    private: 
     shared_ptr<int> m_data; 
    }; 

Ahora tenemos una clase que es poco profunda copiado cuando sólo se necesita el acceso constante, y profundo copia cuando se necesita acceso no constante. Esta es la idea detrás del concepto de puntero "shared_data". const las llamadas no se copiarán en profundidad (lo llaman "separar"), mientras que las no-const copiarán profundamente si se comparte. También añade algunas semántica en la parte superior del operador == para que no se acaba comparando el puntero pero los datos y, por lo que esto funcionaría:

BigArray b=a;//shallow copy 
assert(a==b);//true 
b[0]=a[0]+1;//deep copy 
b[0]=a[0];//put it back 
assert(a==b);//true 

Esta técnica es llamada COW (copiar al escribir) y ha existido desde los albores de C++. También es extremadamente frágil: el ejemplo anterior parece funcionar porque es pequeño y tiene pocos casos de uso. En la práctica, rara vez vale la pena el problema, y ​​de hecho C++ 0x ha depreciado las cadenas COW. Entonces use con precaución.

+0

¿Entonces está insinuando que lo que quiere decir al compartir los datos es COW, y al compartir el puntero está el simple shared_ptr/reference puntero idioms? –

+0

sí. Y si lee los documentos de QT se dice tanto. http://doc.trolltech.com/4.5/qshareddatapointer.html grep "copiar al escribir" –