Рассмотрите a 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>
. Существует ли способ поддержать инкапсуляцию при избавлении от буфера посредника?
С точки зрения изменений кода на основе того, что у вас уже есть, самым простым, вероятно, будет дать Пейдж сеттер, принимающий неконстантную векторную ссылку или указатель, и поменять местами
его на вектор, содержащийся на странице. У вызывающего абонента останется пустой вектор, но поскольку проблема заключается в чрезмерном копировании, предположительно вызывающий не хочет, чтобы сохранял данные:
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 в указанный буфер, и Page может указать свой внутренний вектор в качестве буфера.
Наконец, вы можете «возиться с контейнерами указателей». Сделайте страниц
std :: vector
, затем выполните:
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
имеет скромные накладные расходы по сравнению с Page (он выделяет дополнительную память для небольшого узла, содержащего указатель и счетчик ссылок), но его копирование намного дешевле. Это тоже в TR1, если у вас есть какая-то реализация этого, кроме Boost.
Вы можете ввести третий компонент, который будет содержать все изображения. Книга будет заполнять его, страницы будут считываться с него. Если вы хотите ограничить доступ, вы можете закрыть его и сделать книгу и страницы своими друзьями. Если у вас есть повторяющиеся изображения (скажем, на каждой странице есть нижний и верхний колонтитулы, или на некоторых страницах есть логотип, или что-то еще), вы можете сделать этот третий компонент легковесом, что сделает его еще более эффективным, чем то, к чему вы стремились.
Убедитесь, что вы не открываете все страницы при открытии книги. Это может быть дорого. Пусть каждая страница хранит идентификатор для своих изображений (возможно, пути к файлам), а изображения загружаются только тогда, когда вы действительно хотите просмотреть страницу.
используйте член std :: vector
resize
для первоначальной установки размера буфера, а затем напрямую используйте его буфер, используя адрес front ()
.
std::vector<char> v;
v.resize(size);
strcpy(&v.front(), "testing");
Прямой доступ к буферу для std :: vector
предоставляется: & v.front ()
У меня была бы страница
считывает свои собственные данные непосредственно из источника, а книга
будет читать только ту часть источника, которая необходима для поиска каждой отдельной страницы (и для чтения любых данных, принадлежащих к Книга
в целом, например, название).
Например, в случае хранения данных в каталоге, Книга
будет извлекать список файлов в каталоге. Для каждого файла он передает имя файла конструктору Page
, который открывает файл и загружает его содержимое.
Что касается случая, когда книга хранилась в zip-файле, я делаю некоторые предположения относительно того, как работает библиотека, которую вы используете. Я думаю, вы используете Minizip, с которым я не знаком, но на первый взгляд кажется, что открытие файла через Minizip дает вам ручку. Вы передаете этот дескриптор в unzGoToFirstFile ()
и unzGoToNextFile ()
, чтобы установить активный подфайл в zip-файле (в вашем случае активная страница), и используйте unzReadCurrentFile ( )
для загрузки активного подфайла в буфер. В этом случае ваш класс Book
откроет файл с помощью Minizip и установит для него первый подфайл.Затем он передает дескриптор zip-файла конструктору на странице
, который выполняет работу по чтению подфайла из zip-файла. Книга
затем вызовет 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_
.
Использование std :: vector для хранения данных изображения - плохая идея. Для этой цели я буду использовать необработанный указатель или shared_ptr. Это предотвращает двойное копирование буфера.
Поскольку вы заботитесь о памяти , хранение всех данных изображений в памяти для меня тоже плохая идея. Лучше всего поместить его в отдельный класс. Например, ImageData. Этот класс содержит указатель строки данных изображения. Сначала класс может быть инициализирован путем указания пути к файлу, а данные изображения загружаются с диска, когда это необходимо.