Pregunta

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 devolver una referencia a la lista o un puntero a la lista.Sin embargo, no me entusiasma permitir que la persona que llama cambie los datos, solo quiero dejar que lea los datos.

  • ¿Tengo que elegir entre rendimiento e integridad de datos?
  • Si es así, ¿es mejor en general ir por un camino o es específico del caso?
  • ¿Existen otras alternativas?
¿Fue útil?

Solución

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

Si estás usando una colección que no está indexada por valores ordinales...o crees que tu podría necesitar en algún momento en el futuro cercano...entonces es posible que desees considerar exponer tus propios tipos de iterador y sus asociados. begin()/end() métodos:

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)
{
   // ...
}

Otros consejos

Muchas veces la persona que llama quiere acceder solo para iterar sobre la colección.Saca una página del libro de Ruby y haz de la iteración 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 estás exponiendo nada sobre tu interior, es decir.que incluso tener Una colección.

¿Quizás algo como esto?

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

El beneficio aquí es que es muy, muy simple y tan seguro como en C++.Puedes transmitir esto, como sugiere RobQ, pero no hay nada que puedas hacer que impida que alguien lo haga si no estás copiando.Aquí tendrías que usar const_cast, que es bastante fácil de detectar si lo estás buscando.

Los iteradores, alternativamente, pueden brindarle más o menos lo mismo, pero es más complicado.El único beneficio adicional de usar iteradores aquí (que se me ocurre) es que puede tener una mejor encapsulación.

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

Considere su diseño.¿La persona que llama realmente necesita ver la matriz interna?¿Puedes 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 tiene la intención de buscar en la matriz, ¿podría hacerlo el objeto propietario?

Podrías pasar una referencia al vector de resultados a la función.En algunos compiladores, esto puede resultar en un código ligeramente más rápido.

Recomendaría intentar rediseñar primero, optar por una solución limpia en segundo lugar y, en tercer lugar, optimizar el rendimiento (si es necesario).

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

Si decide cambiar su tipo de colección a otro, sus clientes seguirán trabajando.

Lo que desea es acceso de solo lectura sin copiar todo el bloque de datos.Usted tiene un par de opciones.

En primer lugar, podría simplemente 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 la concreción:no puede cambiar la forma en que almacena los datos internamente sin cambiar la interfaz de su clase.

En segundo lugar, puede devolver punteros constantes 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 getNumItems y protejas contra índices fuera de límites.Además, la constancia de sus punteros se elimina fácilmente y sus datos ahora son de lectura y escritura.

Otra opción es proporcionar un par de iteradores, lo cual es un poco más complejo.Esto tiene las mismas ventajas que los punteros, además de no necesitar (necesariamente) proporcionar una llamada getNumItems, y hay mucho más trabajo involucrado para despojar a los iteradores de su constancia.

Probablemente la forma más sencilla de gestionar esto es mediante el uso de un rango de refuerzo:

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 las ventajas de que los rangos se pueden componer, filtrar, etc., como puede ver en la página sitio web.

Usar const es una opción razonable.Es posible que también desee consultar la biblioteca boost C++ para conocer su implementación de puntero compartido.Proporciona las ventajas de los punteros, es decir.Es posible que deba devolver un puntero compartido a "nulo", lo 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 del puntero compartido fuera constante para prohibir las escrituras.

Si tienes un std::list de datos antiguos (lo que .NET llamaría 'tipos de valores'), entonces devolver una referencia constante a esa lista estará bien (ignorando cosas malas como const_cast)

Si tienes un std::list de punteros (o boost::shared_ptr's) entonces eso sólo impedirá que modifiques la colección, no los elementos en la colección.Mi C++ está demasiado oxidado para poder darte la respuesta en este momento :-(

Sugiero usar devoluciones de llamada en la línea de EnumChildWindows.Tendrás que encontrar algún medio para evitar que el usuario cambie tus datos.Tal vez use un const puntero/referencia.

Por otro lado, podrías pasar una copia de cada elemento a la función de devolución de llamada sobrescribiendo la copia cada vez.(No deseas generar una copia de toda tu colección.Sólo 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);
}

Los dos artículos siguientes profundizan en algunos de los problemas involucrados y la necesidad de encapsular clases de contenedores.Aunque no proporcionan una solución completa y funcional, esencialmente conducen al mismo enfoque propuesto por Shog9.

Parte 1: Encapsulación y vampiros
Parte 2 (ahora es necesario registrarse gratuitamente para leer esto): Observación de restos de trenes
por Kevlin Henney

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top