Извлечение, а затем передача необработанных данных в другой класс - Как избежать двойного копирования при сохранении инкапсуляции? - PullRequest
5 голосов
/ 25 марта 2010

Рассмотрим class Book с контейнером stl class Page. каждый Page содержит скриншот, например page10.jpg в необработанном виде vector<char>.

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

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

Ответы [ 5 ]

3 голосов
/ 25 марта 2010

С точки зрения изменений кода, основанных на том, что у вас уже есть, простейшим, вероятно, является предоставление Page установщика, принимающего неконстантную векторную ссылку или указатель, и 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 быстр - вы ожидаете, что он просто поменяет указатели буфера и размеры двух векторных объектов.

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

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

Наконец, вы можете "связываться с контейнерами указателей". Сделайте pages a 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
}

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

2 голосов
/ 25 марта 2010

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

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

2 голосов
/ 25 марта 2010

используйте элемент std::vector resize для первоначальной установки размера буфера, а затем используйте его буфер напрямую, используя адрес front().

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

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

1 голос
/ 25 марта 2010

Я бы хотел, чтобы класс 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 голосов
/ 25 марта 2010

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

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

...