c++ performance collections data-integrity

c++ - Alternativas eficientes para exponer una colección



performance collections (10)

En C ++, ¿qué alternativas tengo para exponer una colección, desde el punto de vista del rendimiento y la integridad de los datos?

Mi problema es que quiero devolver una lista interna de datos a la persona que llama, pero no quiero generar una copia. Thant me deja con devolver una referencia a la lista o un puntero a la lista. Sin embargo, no estoy loco por dejar que la persona que llama cambie los datos, solo quiero dejar que lea los datos.

  • ¿Debo elegir entre rendimiento e integridad de datos?
  • Si es así, ¿en general es mejor ir por un camino o es particular al caso?
  • ¿Hay otras alternativas?

Los siguientes dos artículos explican algunos de los problemas involucrados y la necesidad de encapsular clases de contenedores. A pesar de que no proporcionan una solución completa y trabajada, esencialmente conducen al mismo enfoque dado por Shog9.

Parte 1: encapsulación y vampiros
Parte 2 (ahora se requiere inscripción gratuita para leer esto): detección de restos de trenes
por Kevlin Henney


El uso de referencia constante o puntero compartido solo ayudará si el contenido de la colección subyacente no cambia con el tiempo.

Considera tu diseño. ¿La persona que llama realmente necesita ver la matriz interna? ¿Se puede reestructurar el código para que la persona que llama le diga al objeto qué hacer con la matriz? Por ejemplo, si la persona que llama intenta buscar en la matriz, ¿podría hacerlo el propietario?

Podría pasar una referencia al vector de resultado de la función. En algunos compiladores, esto puede dar como resultado un código marginalmente más rápido.

Recomiendo tratar de rediseñar primero, yendo con una solución limpia en segundo lugar, optimizando para el rendimiento en tercer lugar (si es necesario).


Lo que desea es acceso de solo lectura sin copiar toda la masa de datos. Usted tiene un par de opciones.

En primer lugar, puede devolver una referencia constante a cualquiera que sea su contenedor de datos, como se sugirió anteriormente:

const std::vector<T>& getData() { return mData; }

Esto tiene la desventaja de ser concreto: no puede cambiar la manera en que almacena los datos internamente sin cambiar la interfaz de su clase.

En segundo lugar, puede devolver los punteros construidos a los datos reales:

const T* getDataAt(size_t index) { return &mData[index]; }

Esto es un poco mejor, pero también requiere que proporciones una llamada a getNumItems y que te protejas contra los índices fuera de límites. Además, la consistencia de sus punteros se descarta fácilmente, y sus datos ahora son de lectura y escritura.

Otra opción es proporcionar un par de iteradores, que es un poco más complejo. Esto tiene las mismas ventajas que los punteros, así como no (necesariamente) la necesidad de proporcionar una llamada a getNumItems, y hay mucho más trabajo involucrado para despojar a los iteradores de su const-ness.

Probablemente la forma más fácil de gestionar esto es mediante el uso de un Boost Range:

typedef vector<T>::const_iterator range_iterator_type; boost::iterator_range< range_iterator_type >& getDataRange() { return boost::iterator_range(mData.begin(), mData.end()); }

Esto tiene la ventaja de que los rangos son compostables, filtrables, etc., como puede ver en el sitio web .


Si tiene una std::list estándar de datos viejos simples (lo que .NET llamaría ''tipos de valor''), entonces devolver una referencia constante a esa lista estará bien (ignorando cosas malvadas como const_cast )

Si tiene una std::list boost::shared_ptr de punteros (o boost::shared_ptr ), eso solo le impedirá modificar la colección, no los elementos de la colección. Mi C ++ está demasiado oxidado como para poder decirle la respuesta a eso en este momento :-(


Tal vez algo como esto?

const std::vector<mydata>& getData() { return _myPrivateData; }

El beneficio aquí es que es muy, muy simple y tan seguro como getin C ++. Puedes lanzar esto, como sugiere RobQ, pero no hay nada que puedas hacer para evitar que alguien lo haga si no estás copiando. Aquí, debería usar const_cast , que es bastante fácil de detectar si lo está buscando.

Iteradores, alternativamente, pueden obtener más o menos lo mismo, pero es más complicado. El único beneficio adicional de usar iteradores aquí (que puedo pensar) es que puedes tener una mejor encapsulación.


Una de las ventajas de las soluciones de @ Shog9 y @ RichQ es que desvincularon al cliente de la implementación de la colección.

Si decide cambiar su tipo de colección a otra cosa, sus clientes seguirán funcionando.


Usar const es una elección razonable. También es posible que desee consultar la biblioteca C ++ boost para su implementación de puntero compartido. Proporciona las ventajas de los punteros, es decir, puede tener el requisito de devolver un puntero compartido a "nulo" que una referencia no permitiría.

http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm

En su caso, haría que el tipo de puntero compartido const para prohibir escrituras.


Sugiero usar devoluciones de llamada a lo largo de las líneas de EnumChildWindows . Deberá encontrar algunos medios para evitar que el usuario cambie sus datos. Tal vez use un puntero / referencia const .

Por otro lado, podría pasar una copia de cada elemento a la función de devolución de llamada sobrescribiendo la copia cada vez. (No desea generar una copia de toda su colección. Solo sugiero hacer una copia de un elemento a la vez. Eso no debería tomar mucho tiempo / memoria).

MyClass tmp; for(int i = 0; i < n; i++){ tmp = elements[i]; callback(tmp); }


Muchas veces la persona que llama quiere acceso solo para iterar sobre la colección. Saca una página del libro de Ruby y haz que la iteración sea un aspecto privado de tu clase.

#include <algorithm> #include <boost/function.hpp> class Blah { public: void for_each_data(const std::function<void(const mydata&)>& f) const { std::for_each(myPreciousData.begin(), myPreciousData.end(), f); } private: typedef std::vector<mydata> mydata_collection; mydata_collection myPreciousData; };

Con este enfoque no expones nada acerca de tus partes internas, es decir, que incluso tienes una colección.


La respuesta de RichQ es una técnica razonable, si está usando una matriz, un vector, etc.

Si está utilizando una colección que no está indexada por valores ordinales ... o cree que podría necesitarla en algún momento en el futuro cercano ... entonces podría considerar exponer su propio tipo de iterador, y métodos asociados de begin() / end() :

class Blah { public: typedef std::vector<mydata> mydata_collection; typedef myDataCollection::const_iterator mydata_const_iterator; // ... mydata_const_iterator data_begin() const { return myPreciousData.begin(); } mydata_const_iterator data_end() const { return myPreciousData.end(); } private: mydata_collection myPreciousData; };

... que luego puedes usar de la manera normal:

Blah blah; for (Blah::mydata_const_iterator itr = blah.data_begin(); itr != blah.data_end(); ++itr) { // ... }