2010-03-25 8 views
5

Rozważ class Book z pojemnikiem STL o numerze class Page. każdy plik Page zawiera zrzut ekranu, na przykład page10.jpg w postaci nieprzetworzonej vector<char>.Wyodrębnianie, a następnie przekazywanie nieprzetworzonych danych do innej klasy - Jak uniknąć dwukrotnego kopiowania podczas utrzymywania enkapsulacji?

Book otwiera się ze ścieżką do zamka, RAR lub katalogu zawierające te zdjęcia i wykorzystuje odpowiednie sposoby ekstrakcji danych surowych, jak ifstream inFile.read(buffer, size); lub unzReadCurrentFile(zipFile, buffer, size). Następnie wywołuje konstruktor Page(const char* stream, int filesize).

Obecnie jasne jest, że surowe dane są kopiowane dwukrotnie. Raz, aby wyodrębnić do Book's local buffer i po raz drugi w Page ctor do Page::vector<char>. Czy istnieje sposób na utrzymanie hermetyzacji podczas pozbycia się bufora pośredniego?

Odpowiedz

3

Jeśli chodzi o zmiany w kodzie w oparciu o to, co już masz, najprostsze jest najprawdopodobniej, aby ustawić Page Setter, przyjmując wektor lub wskaźnik wektora niestałego, oraz swap go z wektorem zawartym na stronie. Rozmówca będzie pozostawiony gospodarstwa pusty wektor, ale ponieważ problem jest nadmierne kopiowanie, przypuszczalnie rozmówca nie chce zachować dane:

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

niektórzy ludzie (na przykład Google C++ Podręcznik stylu) chcą parametry referencyjne być const, i chciałby, aby przekazać parametr newcontent jako wskaźnik, aby podkreślić, że jest non-const:

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

swap jest szybka - można się spodziewać, że po prostu wymienić bufor wskaźniki i rozmiary dwóch obiektów wektorowych.

Alternatywnie, podaj stronę dwa różne konstruktory: jeden dla pliku zip i drugi dla zwykłego pliku oraz czy odpowiada za odczyt własnych danych. Jest to prawdopodobnie najczystsze i pozwala Strona być niezmienna, a nie modyfikowana po zakończeniu budowy. W rzeczywistości możesz tego nie chcieć, ponieważ, jak zauważyłeś w komentarzu, dodanie strony do kontenera powoduje skopiowanie strony. Istnieje więc pewna korzyść z możliwości modyfikowania strony, aby dodać dane, które zostały tanio zbudowane w kontenerze: unika tej dodatkowej kopii bez potrzeby bałagania w pojemnikach ze wskaźnikami. Mimo to funkcja setContent może równie łatwo wziąć informacje o strumieniu pliku/pliku zip, co zrobić wektor.

Można znaleźć lub napisać klasę strumienia, która czyta z pliku zip, aby strona mogła być odpowiedzialna za odczyt danych za pomocą tylko jednego konstruktora pobierającego strumień. A może nie cała klasa strumieniowa, może tylko interfejs, który projektujesz, który odczytuje dane ze strumienia/zip/rar do określonego bufora, a Strona może określać swój wewnętrzny wektor jako bufor.

W końcu możesz "zadzieżyć z pojemnikami ze wskaźnikami". Dokonaj pages się std::vector<boost::shared_ptr<Page> >, a następnie wykonaj:

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 ma skromną napowietrznych względem tylko strony (to sprawia dodatkową alokację pamięci dla małego węzła zawierającego wskaźnik i RefCount), ale jest znacznie tańsze do skopiowania. Jest również w TR1, jeśli masz implementację tego innego niż Boost.

+0

No dobra, nie myślałem o używaniu zamiany. Myślałem o tym, czy strona zajmie się odczytem danych. Rzecz w tym, że ciągle otwierałem plik zip, przeskakując do i czytając 1 obraz i zamykając go. Co gorsza biblioteka unrar nie obsługuje przeskakiwania do elementów. – Kache

+0

"Ciągle otwierałem plik zip, przeskakując do i czytając 1 obraz i zamykając go" - niekoniecznie. Przypuszczalnie twoja klasa "Book" obecnie przechodzi przez zip/rar czytając jeden plik na raz. Konstruktor 'Strona' może mieć takie samo zachowanie, że" pochłania "niektóre dane z obiektu przekazanego do niego. Po prostu bądź ostrożny w obsłudze błędów - ponieważ przeczytałeś niektóre dane i prawdopodobnie nie ma możliwości przywrócenia położenia strumienia do miejsca, w którym zostało rozpoczęte (jeśli nie możesz przeskoczyć do pliku, nie możesz szukać), możesz tylko zaoferować słaba gwarancja wyjątku. –

2

użyć elementu std::vectorresize, aby ustawić pierwotnie rozmiar bufora, a następnie użyć jego bufora bezpośrednio, korzystając z adresu front().

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

Bezpośredni dostęp bufor std::vector jest dana przez: &v.front()

+0

Mówisz, że powinienem zrobić "publiczny"? – Kache

+0

Możesz lub możesz po prostu zwrócić 'char *' 'i v.front() ', który jest początkowym adresem' wektora'. –

+0

Uwaga: Możesz także użyć konstruktora wektora zamiast zmieniać jego rozmiar. –

0

Można wprowadzić trzeci składnik, który pomieścić wszystkie obrazy. Książka wypełni go, strony będą czytać z niego. Jeśli chcesz ograniczyć dostęp, możesz go zamknąć, utworzyć książkę i zaprosić znajomych. Jeśli masz powtórzenia obrazów (powiedzmy, że każda ze stron ma stopkę i nagłówek, lub niektóre strony mają logo lub cokolwiek innego), możesz uczynić ten trzeci element wagą muszki, co czyni go jeszcze bardziej wydajnym niż to, do czego dążyłeś.

Upewnij się, że nie otwierasz wszystkich stron po otwarciu książki. To może być kosztowne. Każda strona zawiera identyfikator swoich obrazów (może ścieżek plików) i ładuje obrazy tylko wtedy, gdy naprawdę chcesz wyświetlić stronę.

+0

To jest ciekawy pomysł. Wszystkie surowe dane są zarządzane i enkapsulowane w jednym miejscu, a także współdziałają z zewnątrz poprzez książkę i stronę. Co miałeś na myśli przez muszkę? Zastanawiałem się również nad niską resą i/lub miniaturą dla obrazów, i myślałem o tym, jak to zaprojektować. – Kache

+0

Zobacz http://en.wikipedia.org/wiki/Flyweight_pattern, aby uzyskać informacje na temat wzoru wzoru muszki. W gruncie rzeczy chodzi o to, aby udostępnić jak najwięcej informacji w puli obiektów (w twoim przypadku, na stronach książki), aby zaoszczędzić pamięć. Na przykład, wszystkie strony będą miały nagłówek i stopkę, ale ponieważ są identyczne na stronach, strony będą udostępniać te obiekty. Jeśli strony nie są właścicielami tych obiektów (tak jak ma to miejsce w przypadku wyciągnięcia tej informacji do trzeciego komponentu), to nawet nie masz problemu z tym, kto powinien zniszczyć te dane, gdy skończysz. – wilhelmtell

2

Używanie std :: vector do przechowywania danych obrazu to zły pomysł. W tym celu użyję surowego wskaźnika lub shared_ptr. Zapobiega to dwukrotnemu kopiowaniu bufora.

Odtąd do pamięci opieki, przechowywanie wszystkich danych obrazu w pamięci jest również złym pomysłem dla mnie. Lepszym przypadkiem jest zamknięcie go w osobnej klasie. Na przykład ImageData. Ta klasa zawiera wskaźnik wiersza danych obrazu. Klasę można zainicjować za pomocą ścieżki pliku na początku, a dane obrazu są ładowane z dysku, gdy jest to wymagane.

+0

Ale wtedy 'Book' miałby' stl :: deque' z ImageData, a ImageData musiałaby wtedy zdefiniować konstruktora kopiowania, gdzie dane są w dużym stopniu skopiowane, prawda? – Kache

1

Chciałbym, aby klasa odczytywała własne dane bezpośrednio ze źródła, a Book odczytałaby tylko tyle ze źródła, ile potrzebowałaby, aby zlokalizować każdą pojedynczą stronę (i odczytać wszystkie dane należące do Book ogólnie, na przykład tytuł).

Na przykład w przypadku danych przechowywanych w katalogu, Book pobierał listę plików w katalogu. Dla każdego pliku przekazywałoby to nazwę pliku do konstruktora Page, który otwierałby plik i ładował jego zawartość.

Jeśli chodzi o przypadek, w którym książka była przechowywana w pliku zip, zastanawiam się, jak działa biblioteka, z której korzystasz. Myślę, że używasz Minizip, z którym nie jestem zaznajomiony, ale na pierwszy rzut oka wygląda na to, że otwarcie pliku przez Minizip daje ci uchwyt. Przekaż ten uchwyt do unzGoToFirstFile() i unzGoToNextFile(), aby ustawić aktywny plik fragmentu w pliku zip (w twoim przypadku aktywna strona) i użyj unzReadCurrentFile(), aby załadować aktywny fragment subfile do bufora. Jeśli tak jest, to twoja klasa Book otworzy plik za pomocą Minizip i ustawi go na pierwszym fragmencie. Następnie przekazałby uchwyt do pliku zip do konstruktora w Page, który mógłby wykonać odczyt fragmentu z pliku zip. Book następnie wywoła unzGoToNextFile(), aby przejść do następnego fragmentu, i utworzy kolejną stronę, ponownie przekazując uchwyt do Page. Kontynuowałby to, dopóki nie pozostałyby żadne podteksty. Byłoby to wyglądać mniej więcej tak:

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

Jest to bardzo uproszczona (i nie może być używany Minizip całkowicie błędne, więc uważaj), a także zakłada, że ​​Book sklepy wektorem Page obiektów nazwanych pages_ oraz że Page nazwy jego bufor data_.

+0

Chodzi o to, że książka składa się z wielu stron zrzuty ekranu, wszystko w pliku zip lub rar. Konceptualnie Księga powinna posiadać zamek lub rar. Jednak myślę, że mogę być w stanie przekazać referencję dla uchwytu zip/rar do każdej strony i każda Strona będzie zlokalizować i wyodrębnić surowe dane, których potrzebuje. Ewentualnie mógłbym zapisać odniesienie do Księgi na każdej stronie, tak aby każda strona mogła sama zdecydować, kiedy musi dotrzeć i chwycić za uchwyt zip/rar. – Kache

+0

Tak, właśnie to myślałem; Księga otworzyłaby kontener (katalog, plik ZIP, plik RAR itp.) i przekazał uchwyt tego kontenera do konstruktora stron, który następnie załadowałby pojedynczą stronę z kontenera. Myślałem o tym trochę więcej, a to może nie być dobry pomysł. Za każdym razem, gdy chcesz dodać nowy typ kontenera, musisz zaktualizować zarówno książkę, jak i stronę. Obaj będą odpowiedzialni za poznanie działania każdego kontenera. Lepszym pomysłem byłoby, gdyby Księgę załadowała wszystkie załadowane dane i przekazała je na Stronę za odpowiedzią Steve'a Jessopa. –

Powiązane problemy