2008-09-04 8 views
12

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

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.

  • ¿Tengo que elegir entre el rendimiento y la integridad de datos?
  • Si es así, ¿en general es mejor ir por un camino o es particular del caso?
  • ¿Hay otras alternativas?

Respuesta

5

RichQ's answer es una técnica razonable, si está utilizando una matriz, vector, etc.

Si estás usando una colección que no está indexado por los valores ordinales ... o piensa que puede ser que necesite a en algún momento en el futuro cercano ... entonces es posible que desee considerar la exposición de su propio tipo (s) iterador, y asociado 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 se puede utilizar en la moda normal:

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

Quizás algo como esto?

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

La ventaja aquí es que es muy, muy simple, y tan seguro como usted 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í, tendría que 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.

0

Usar const es una opción razonable. Es posible que también desee consultar la biblioteca C++ boost para la implementación de su 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 el caso de que haría que const tipo del puntero compartida para prohibir las escrituras.

0

Si usted tiene un std::list de datos (Plain Old .NET lo llamarían 'tipos de valor'), a continuación, devolver una referencia constante a esa lista va a estar bien (haciendo caso omiso de las cosas malas como const_cast)

Si tiene un std::list de punteros (o boost::shared_ptr) entonces eso solo le impedirá modificar la colección, no los artículos en la colección.Mi C++ es demasiado oxidado a ser capaz de decir que la respuesta a eso en este momento :-(

2

El uso de referencia constante o un puntero compartida sólo ayudará si el contenido de la colección subyacente no cambian con el tiempo.

Considere su diseño. ¿Necesita realmente la persona que llama ver la matriz interna? ¿Puede reestructurar el código para que la persona que llama le diga qué hacer con la matriz? Por ejemplo, si la persona que llama intenta buscar la matriz, ¿podría el objeto propietario hacerlo? ?

Puede pasar una referencia al vector de resultados de la función En algunos compiladores que pueden dar como resultado un código ligeramente más rápido

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

1

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

En primer lugar, sólo podría devolver un refererence const a lo que su contenedor de datos es, como se ha sugerido anteriormente:

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

Esto tiene la desventaja de concreción: no se puede cambiar la forma de almacenar los datos internamente sin cambiando la interfaz de tu clase.

En segundo lugar, puede devolver punteros const-ed para los datos reales:

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

Esto es un poco más agradable, sino que también requiere que se proporcione un getNumItems llaman, y protegerla contra los índices fuera de los 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 manejar esto es mediante el uso de un rango de realce:

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 son rangos componibles, filtrable, etc, como se puede ver en la website.

2

Una de las ventajas de las soluciones de @ Shog9 y @ RichQ es que eliminan el par del 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.

7

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 está exponiendo nada acerca de sus partes internas, es decir, que incluso tiene una colección.

+1

Sé que esto es una respuesta de edad, pero lo hago con std :: función ahora: 'for_each_data vacío (const std :: función & f) const;' –

+0

@JoBates Ya no estoy usando C++ día a día, ¡por favor no duden en editar mi respuesta para actualizarla! –

0

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

Parte 1: Encapsulation and Vampires
Parte 2 (libre de ahora se requiere para leer este): Train Wreck Spotting
por Kevlin Henney

+0

FYI los enlaces que publicó están muertos. –

+0

@MM. Gracias por la notificación, ahora corregido. – Skeve

0

sugiero utilizar 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, puede 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); 
} 
Cuestiones relacionadas