2010-03-25 2 views
5

Рассмотрите class Book с контейнером stl class Page. каждый Page имеет скриншот, например page10.jpg in raw vector<char>.Извлечение, затем передача необработанных данных в другой класс. Как избежать копирования при сохранении инкапсуляции?

Book открывается с путем к ZIP, RAR, или каталог, содержащий эти снимки экрана, и использует соответствующие методы извлечения исходных данных, как ifstream inFile.read(buffer, size); или unzReadCurrentFile(zipFile, buffer, size). Затем он вызывает конструктор Page(const char* stream, int filesize).

В настоящее время ясно, что необработанные данные копируются дважды. Однажды, чтобы извлечь в местную книгу buffer и второй раз в Page ctor до Page::vector<char>. Есть ли способ поддерживать инкапсуляцию, избавляясь от буфера посредника?

ответ

3

С точки зрения изменения кода, основанного на том, что у вас уже есть, самым простым, возможно, является предоставление Page set set, принимающего неконстантную векторную ссылку или указатель, и swap с вектором, содержащимся на странице. Вызывающий останется проведение пустой вектор, но поскольку эта проблема является чрезмерное копирование, предположительно вызывающий абонент не хотите сохранить данные:

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); 
    } 
}; 

Некоторые люди (например, Google C++ стиль руководства) хочу эталонные параметры быть константной, и хотели бы вы передать параметр newcontent как указатель, чтобы подчеркнуть, что неконстантная:

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

swap быстро - вы бы ожидать, что это просто заменить буфер указатели и размеры двух векторных объектов.

В качестве альтернативы, дайте Странице два разных конструктора: один для zip-файла и один для обычного файла и нести ответственность за чтение своих собственных данных. Это, пожалуй, самое чистое, и это позволяет Page быть неизменным, а не изменяться после строительства. Но на самом деле вы можете этого не захотеть, поскольку, как вы заметили в комментарии, добавление страницы в контейнер копирует страницу. Таким образом, есть некоторая польза от возможности изменить страницу, чтобы добавить данные после, она была дешево построена в контейнере: она избегает этой дополнительной копии, не требуя прикосновения с контейнерами указателей. Тем не менее, функция setContent может так же легко взять файл/файл zip-файла, как взять вектор.

Вы можете найти или написать класс потока, который читает из zip-файла, так что страница может отвечать за чтение данных только одним конструктором, принимающим поток. Или, возможно, не весь класс потока, возможно, только интерфейс, который вы создаете, который считывает данные из потока/zip/rar в указанный буфер, а страница может указывать свой внутренний вектор в качестве буфера.

И, наконец, вы можете «испортить контейнеры указателей». Сделать pages в std::vector<boost::shared_ptr<Page> >, а затем сделать:

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 
} 

shared_ptr имеет скромные накладные относительно просто Page (это дополнительное выделение памяти для небольшого узла, содержащего указатель и RefCount), но много дешевле скопировать. Это также в TR1, если у вас есть реализация, отличная от Boost.

+0

О, хорошо, я не думал об использовании swap. Я думал о том, что страница обрабатывает данные. Дело в том, что я буду постоянно открывать zipfile, прыгать и читать 1 изображение и закрывать его. Еще хуже то, что библиотека unrar не поддерживает переход к элементам. – Kache

+0

«Я буду постоянно открывать zipfile, прыгать и читать 1 изображение и закрывать его» - не обязательно. Предположительно, ваш класс 'Book' в настоящее время продвигается через zip/rar, просматривая один файл за раз. Конструктор 'Page' может иметь такое же поведение, что он« потребляет »некоторые данные из переданного ему объекта. Просто будьте осторожны при обработке ошибок - потому что вы прочитали некоторые данные, и, вероятно, нет способа вернуть позицию потока туда, где она была запущена (если вы не можете перейти к файлу, вы не можете искать), вы можете только предложить слабая гарантия исключения. –

2

участник std::vectorresize, чтобы установить размер буфера изначально, а затем использовать его буфер непосредственно с помощью адреса front().

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

Прямой доступ буфера std::vector определяется по формуле: &v.front()

+0

Вы говорите, что я должен сделать 'Page :: vector ' public? – Kache

+0

Вы можете или можете просто вернуть символ 'char *' '& v.front() ', который является начальным адресом' vector'. –

+0

Примечание: вы можете также использовать конструктор вектора вместо изменения размера. –

0

Вы можете ввести третий компонент, который будет держать все изображения. Книга заполнит его, страницы будут прочитаны из него. Если вы хотите ограничить доступ, вы можете закрыть его и сделать книгу и перечислить ее друзей. Если у вас есть повторения изображений (например, на каждой странице есть нижний колонтитул и заголовок, или на некоторых страницах есть логотип или что-то еще), вы можете сделать этот третий компонент мухой, что сделает его еще более эффективным, чем то, к чему вы стремились.

Убедитесь, что вы не открываете все страницы при открытии книги. Это может быть дорого. У каждой страницы есть идентификатор для своих изображений (возможно, путь к файлам) и загружать изображения только тогда, когда вы действительно хотите просмотреть страницу.

+0

Это интересная идея. Все необработанные данные управляются и инкапсулируются в одном месте и взаимодействуют снаружи через книгу и страницу. Что вы имели в виду от мухи? Я рассматривал возможность получения снимков с низким разрешением и/или уменьшенных изображений для изображений, и я думал о том, как это сделать для этого. – Kache

+0

См. Http://en.wikipedia.org/wiki/Flyweight_pattern для получения информации о шаблоне дизайна мухи. По сути, идея состоит в том, чтобы поделиться как можно большей информацией между пулом объектов (в вашем случае, страницами книги), чтобы сохранить память. Так, например, все страницы будут иметь заголовок и нижний колонтитул, но поскольку они одинаковы среди страниц, страницы будут обмениваться этими объектами. Если страницы не владеют этими объектами (как это бывает при выводе этой информации на третий компонент), то у вас даже нет проблемы с тем, кто должен уничтожить эти данные, когда вы закончите с этим. – wilhelmtell

2

Использование std :: vector для хранения данных изображения - плохая идея. Для этой цели я буду использовать raw-указатель или shared_ptr. Это предотвращает копирование буфера дважды.

Поскольку вы do Уход за памятью, хранение всех данных изображения в памяти - также плохая идея для меня. Лучше всего заключить его в отдельный класс. Например, ImageData. Этот класс содержит указатель строки данных изображения. Класс может быть инициализирован с помощью пути к файлу в начале, и данные изображения загружаются с диска, когда это необходимо.

+0

Но тогда 'Book' будет иметь' stl :: deque' ImageData, и тогда ImageData должен будет определить конструктор копирования, где данные будут жестко скопированы, так? – Kache

1

Я бы иметь Page класс прочитать свои собственные данные непосредственно из источника, и Book читать только бы столько источника, необходимым ему для того, чтобы найти каждую отдельную страницу (и читать любые данные, принадлежащие Book в общем, например, название).

Например, в случае данных, хранящихся в каталоге, Book будет извлекать список файлов в каталоге. Для каждого файла он передаст имя файла в конструктор Page, который откроет файл и загрузит его содержимое.

Что касается случая, когда книга хранилась в zip-файле, я делаю некоторые догадки о том, как работает библиотека, которую вы используете. Я думаю, вы используете Minizip, с которым я не знаком, но с первого взгляда похоже, что открытие файла через Minizip дает вам ручку. Вы передаете этот дескриптор unzGoToFirstFile() и unzGoToNextFile(), чтобы установить активный подфайл в zip-файле (в вашем случае, на активной странице) и использовать unzReadCurrentFile() для загрузки активного подфайла в буфер. Если это так, то ваш класс Book откроет файл с помощью Minizip и установит его в первый подфайл. Затем он передал дескриптор zip-файлу в конструктор в Page, который выполнит работу по чтению подфайла из zip-файла. Затем Book вызовет unzGoToNextFile(), чтобы перейти к следующему подфайлу, и создаст другую страницу, снова передав дескриптор в Page. Он будет продолжать делать это до тех пор, пока не останется никаких подфайлов. Это будет выглядеть примерно так:

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); 
    } 
} 

Это очень упрощена (и я мог бы использовать Minizip совершенно неправильно, так что будьте осторожны), и это также предполагает, что Book хранит вектор Page объектов, названных pages_, и что Page имена его буфер data_.

+0

Дело в том, что Книга состоит из многих страниц скриншотов, все в zip-файле или rar-файле. Концептуально, Книга должна иметь zip или rar. Тем не менее, я думаю, что я могу передать ссылку для дескриптора zip/rar на каждую страницу и каждую страницу найти и извлечь необработанные данные, которые ему нужны. В качестве альтернативы, я мог бы хранить ссылку на книгу на каждой странице, чтобы каждая страница могла самостоятельно решить, когда ей нужно протянуть руку и захватить дескриптор zip/rar. – Kache

+0

Да, вот что я думаю; Книга откроет контейнер (каталог, ZIP-файл, RAR-файл и т. д.) и передаст дескриптор этого контейнера конструктору страницы, который затем будет загружать одну страницу из контейнера. Я подумал об этом еще немного, и это может быть не очень хорошая идея. Каждый раз, когда вы хотите добавить новый тип контейнера, вам придется обновлять как книгу, так и страницу. Оба будут отвечать за знание того, как работает каждый контейнер. Вероятно, было бы лучше, если бы книга выполнила всю загрузку и передала загруженные данные на страницу, за ответ Стива Джессопа. –