2010-03-25 4 views
5

Considere un class Book con un contenedor STL de class Page. cada Page contiene una captura de pantalla, como page10.jpg en formato raw vector<char>.Extrayendo y pasando datos sin procesar a otra clase. ¿Cómo evitar copiar dos veces mientras se mantiene la encapsulación?

A Book se abre con una ruta a un zip, rar, o directorio que contiene estas capturas de pantalla, y utiliza los métodos respectivos de la extracción de los datos en bruto, como ifstream inFile.read(buffer, size);, o unzReadCurrentFile(zipFile, buffer, size). Luego llama al constructor Page(const char* stream, int filesize).

En este momento, está claro que los datos brutos se están copiando dos veces. Una vez para extraer al local de Book buffer y una segunda vez en el Page ctor al Page::vector<char>. ¿Hay alguna manera de mantener la encapsulación mientras se deshace del intermediario?

Respuesta

3

En términos de cambios de código basados ​​en lo que ya ha hecho, lo más simple es dar a la Página un setter tomando una referencia de vector no const o puntero, y swap con el vector contenido en la Página. La persona que llama se queda con un vector vacío, pero ya que el problema es la copia excesiva, presumiblemente la persona que llama no quiere para mantener los datos:

void Book::addPage(ifstream file, streampos size) { 
    std::vector<char> vec(size); 
    file.read(&vec[0], size); 
    pages.push_back(Page()); // pages is a data member 
    pages.back().setContent(vec); 
} 

class Page { 
    std::vector<char> content; 
public: 
    Page() : content(0) {} // create an empty page 
    void setContent(std::vector<char> &newcontent) { 
     content.swap(newcontent); 
    } 
}; 

Algunas personas (por ejemplo, el Google C++ guía de estilo) quieren parámetros de referencia para ser const, y querría que pasa el parámetro newcontent como un puntero, al destacar que es no constante:

void setContent(std::vector<char> *newcontent) { 
    content.swap(*newcontent); 
} 

swap es rápido - se espera también sólo para intercambiar el tampón punteros y tamaños de los dos objetos vectoriales.

O bien, proporcione a Page dos constructores diferentes: uno para el archivo zip y otro para el archivo normal, y hágalo responsable de leer sus propios datos. Este es probablemente el más limpio, y permite que Page sea inmutable, en lugar de modificarse después de la construcción. Pero, en realidad, es posible que no lo desee, ya que como ha notado en un comentario, al agregar la Página a un contenedor, se copia la Página. Por lo tanto, hay algunos beneficios de poder modificar la página para agregar los datos después de que se ha fabricado de forma económica en el contenedor: evita esa copia adicional sin necesidad de meterse con los contenedores de punteros. Aún así, la función setContent podría tomar fácilmente la información del archivo/archivo zip como tomar un vector.

Puede buscar o escribir una clase de flujo que lea desde un archivo zip, de modo que Page puede ser responsable de leer los datos con solo un constructor tomando una secuencia. O tal vez no una clase de flujo completo, tal vez solo una interfaz que diseñe, que lea datos de un flujo/zip/rar en un búfer especificado, y Page pueda especificar su vector interno como el búfer.

Finalmente, podría "meterse con contenedores de punteros". Hacer pages un std::vector<boost::shared_ptr<Page> >, a continuación, hacer:

void Book::addPage(ifstream file, streampos size) { 
    boost::shared_ptr<Page> page(new Page(file, size)); 
    pages.push_back(page); // pages is a data member 
} 

Un shared_ptr tiene un pariente sobrecarga modesta de sólo una página (que tiene una asignación de memoria adicional para un pequeño nodo que contiene un puntero y una refcount), pero es mucho más barato de copiar. También está en TR1, si tiene alguna implementación de eso aparte de Boost.

+0

Oh, bueno, no pensé en usar swap. Pensé en tener a Page manejar la lectura de datos. La cosa con eso estaría constantemente abriendo un archivo zip, saltando y leyendo una imagen, y cerrándola. Peor aún, la biblioteca unrar no admite saltar a los artículos. – Kache

+0

"Abría constantemente un archivo zip, saltaba y leía 1 imagen y la cerraba", no necesariamente. Presumiblemente, su clase 'Book' actualmente avanza por el zip/rar leyendo un archivo a la vez. El constructor 'Page' podría tener el mismo comportamiento, que" consume "algunos datos del objeto que se le pasa. Solo tenga cuidado al manejar los errores, porque ha leído algunos datos y probablemente no haya forma de volver a poner la posición del flujo en el punto inicial (si no puede saltar a un archivo, no puede buscar), solo puede ofrecer la débil garantía de excepción. –

2

use std::vectorresize miembro para establecer originalmente el tamaño del búfer y luego use su búfer directamente usando la dirección front().

std::vector<char> v; 
v.resize(size); 
strcpy(&v.front(), "testing"); 

acceso al buffer directa de std::vector viene dada por: &v.front()

+0

¿Estás diciendo que debería hacer público el 'Page :: vector '? – Kache

+0

Puede o puede simplemente devolver un 'char *' de '& v.front() 'que es la dirección inicial del' vector'. –

+0

Nota: También puede usar el constructor vectorial en lugar de cambiar el tamaño. –

0

Puede introducir un tercer componente que contendría todas las imágenes. El libro lo llenaría, las páginas leerían de él. Si desea restringir el acceso, puede cerrarlo y hacer que el libro y la página sean amigos. Si tiene repeticiones de imágenes (por ejemplo, cada página tiene un pie de página y un encabezado, o algunas páginas tienen un logotipo, o lo que sea) puede hacer que ese tercer componente sea un peso mosca, lo que lo hace aún más eficiente que lo que se esforzó.

Asegúrese de no abrir todas las páginas al abrir el libro. Eso puede ser costoso Haga que cada página contenga un identificador para sus imágenes (quizás rutas de archivos), y cargue las imágenes solo cuando realmente desee ver la página.

+0

Esta es una idea interesante. Todos los datos brutos se gestionan y encapsulan en un solo lugar e interactúan desde el exterior a través de Libro y Página. ¿Qué quisiste decir con flyweight? También estaba considerando tener una imagen en baja resolución y/o una miniatura para las imágenes, y estaba pensando en cómo diseñar para eso también. – Kache

+0

Consulte http://en.wikipedia.org/wiki/Flyweight_pattern para obtener información sobre el patrón de diseño flyweight. En esencia, la idea es compartir la mayor cantidad de información posible entre un conjunto de objetos (en su caso, las páginas de un libro) para ahorrar memoria. Entonces, por ejemplo, todas las páginas tendrán un encabezado y un pie de página, pero debido a que son idénticas entre las páginas, las páginas compartirán estos objetos. Si las páginas no son propietarias de estos objetos (como es el caso cuando extraes esta información a un tercer componente), entonces ni siquiera tienes el problema de quién debería destruir estos datos cuando hayas terminado con ellos. – wilhelmtell

2

Usar std :: vector para almacenar datos de imágenes es una mala idea. Utilizaré puntero sin procesar o shared_ptr para este propósito. Esto evita que el buffer se copie dos veces.

Dado que do memoria de atención, la celebración de todos los datos de imagen en la memoria también es una mala idea para mí. Un mejor caso es encapsularlo en una clase separada. Por ejemplo, ImageData. Esta clase contiene el puntero de fila de los datos de imagen. La clase se puede inicializar con una ruta de archivo al principio, y los datos de imagen se cargan desde el disco cuando es necesario.

+0

Pero entonces 'Book' tendría un' stl :: deque' de ImageData, y ImageData tendría que definir un constructor de copia donde los datos son copiados de todas maneras, ¿verdad? – Kache

1

que tendría la clase Page leer sus propios datos directamente de la fuente, y la Book sólo sería leer tanto de la fuente que era necesario con el fin de localizar a cada página individual (y de leer todos los datos pertenecientes a la Book en general, como un título).

Por ejemplo, en el caso de los datos almacenados en un directorio, el Book recuperaría la lista de archivos en el directorio. Para cada archivo, pasaría el nombre de archivo a un constructor Page que abriría el archivo y cargaría su contenido.

En cuanto al caso en que el libro se almacenó en un archivo zip, estoy haciendo algunas conjeturas sobre cómo funciona la biblioteca que está utilizando. Creo que estás usando Minizip, con el que no estoy familiarizado, pero a primera vista parece que abrir un archivo a través de Minizip te da un control. Pasa ese asa a unzGoToFirstFile() y unzGoToNextFile() para establecer el subarchivo activo dentro del archivo zip (en tu caso, la página activa), y usa unzReadCurrentFile() para cargar el subarchivo activo en un búfer. Si ese es el caso, entonces su clase Book abrirá el archivo usando Minizip y lo establecerá en el primer subarchivo. A continuación, pasaría el identificador al archivo comprimido a un constructor en Page, que haría el trabajo de leer el subarchivo del archivo zip. El Book llamaría al unzGoToNextFile() para pasar al siguiente subarchivo y crearía otra página pasando nuevamente el controlador al Page. Continuaría haciendo esto hasta que no quedaran subarchivos. Se vería algo como:

Page::Page(zipFile file) 
{ 
    // TODO: Determine the required size of the buffer that will store the data 
    unsigned buffer_size; 

    data_.resize(buffer_size) 

    unzReadCurrentFile(file, &data_[0], buffer_size); 
} 

void Book::open(const std::string &filename) 
{ 
    zipFile file = unzOpen(filename.c_str()); 

    int result = unzGoToFirstFile(file); 
    while (result == UNZ_OK) 
    { 
     pages_.push_back(Page(file)); 
     unzGoToNextFile(file); 
    } 
} 

Esto es muy simplificado (y yo podría estar utilizando Minizip totalmente equivocado, así que ten cuidado), y también se asume que Book almacena un vector de Page objetos nombrados pages_, y que Page nombres su memoria intermedia data_.

+0

El hecho es que un libro está compuesto de muchas páginas de capturas de pantalla, todas dentro de un archivo zip o rar. Conceptualmente, el libro debería tener el zip o rar. Sin embargo, creo que puedo pasar una referencia para el control zip/rar a cada página y hacer que cada página ubique y extraiga los datos brutos que necesita. Alternativamente, podría almacenar una referencia al libro en cada página, de modo que cada página pueda decidir por sí misma cuándo necesita llegar y tomar el control zip/rar también. – Kache

+0

Sí, eso fue lo que pensé; el Libro abriría el contenedor (un directorio, un archivo ZIP, un archivo RAR, etc.) y pasaría el identificador de ese contenedor a un constructor de Página, que luego cargaría una sola página del contenedor. Sin embargo, he pensado en esto un poco más, y esa podría no ser una buena idea. Cada vez que quisiera agregar un nuevo tipo de contenedor, tendría que actualizar tanto el Libro como la Página. Ambos serían responsables de saber cómo funciona cada contenedor. Probablemente sería una mejor idea hacer que el Libro haga toda la carga y pase los datos cargados a la Página, según la respuesta de Steve Jessop. –

Cuestiones relacionadas