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

Рассмотрите 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>. Существует ли способ поддержать инкапсуляцию при избавлении от буфера посредника?

5
задан Kache 25 March 2010 в 00:18
поделиться

5 ответов

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

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.

3
ответ дан 14 December 2019 в 04:34
поделиться

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

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

0
ответ дан 14 December 2019 в 04:34
поделиться

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

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

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

2
ответ дан 14 December 2019 в 04:34
поделиться

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

Например, в случае хранения данных в каталоге, Книга будет извлекать список файлов в каталоге. Для каждого файла он передает имя файла конструктору 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_ .

1
ответ дан 14 December 2019 в 04:34
поделиться

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

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

2
ответ дан 14 December 2019 в 04:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: