Menggunakan pola Proxy dengan iterator C++

Saya memiliki iterator yang cukup rumit yang membungkus api FindXFile di Win32. (Lihat pertanyaan sebelumnya) Untuk menghindari overhead pembuatan objek yang pada dasarnya menduplikasi pekerjaan struktur WIN32_FIND_DATAW, saya memiliki objek proxy yang hanya bertindak sebagai semacam referensi const ke WIN32_FIND_DATAW tunggal yang dideklarasikan di dalam bagian dalam iterator yang tidak dapat disalin. Ini bagus karena

  1. Klien tidak membayar untuk pembuatan informasi tidak relevan yang mungkin tidak akan mereka gunakan (seringnya orang hanya tertarik pada nama file), dan
  2. Klien bisa mendapatkan semua informasi yang disediakan oleh API FindXFile jika mereka membutuhkan atau menginginkan informasi ini.

Hal ini menjadi masalah karena hanya ada satu salinan data aktual objek tersebut. Oleh karena itu, ketika iterator bertambah, semua proksi menjadi tidak valid (disetel ke file berikutnya yang ditunjuk oleh iterator).

Saya khawatir jika ini merupakan masalah besar, karena saya dapat memikirkan kasus di mana objek proxy tidak berperilaku seperti yang diharapkan seseorang:

std::vector<MyIterator::value_type> files;
std::copy(MyIterator("Hello"), MyIterator(), std::back_inserter(files));

karena vektor hanya berisi sekumpulan proxy yang tidak valid pada saat itu. Sebaliknya, klien perlu melakukan sesuatu seperti:

std::vector<std::wstring> filesToSearch;
std::transform(
 DirectoryIterator<FilesOnly>(L"C:\\Windows\\*"),
 DirectoryIterator<FilesOnly>(),
 std::back_inserter(filesToSearch),
 std::mem_fun_ref(&DirectoryIterator<FilesOnly>::value_type::GetFullFileName)
);

Melihat ini, saya dapat memahami mengapa seseorang mungkin tidak menyukai apa yang dilakukan oleh desainer perpustakaan standar dengan std::vector<bool>. Saya masih bertanya-tanya: apakah ini merupakan trade off yang masuk akal untuk mencapai (1) dan (2) di atas? Jika tidak, apakah masih ada cara untuk mencapai (1) dan (2) tanpa proksi?

EDIT: Kode yang diperbarui:

#pragma once
#include <list>
#include <string>
#include <boost/noncopyable.hpp>
#include <boost/make_shared.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <Windows.h>
#include <Shlwapi.h>
#pragma comment(lib, "shlwapi.lib")
#include "../Exception.hpp"

namespace WindowsAPI { namespace FileSystem {

class Win32FindData {
    WIN32_FIND_DATA internalData;
    std::wstring rootPath;
public:
    Win32FindData(const std::wstring& root, const WIN32_FIND_DATA& data) :
        rootPath(root), internalData(data) {};
    DWORD GetAttributes() const {
        return internalData.dwFileAttributes;
    };
    bool IsDirectory() const {
        return (internalData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
    };
    bool IsFile() const {
        return !IsDirectory();
    };
    unsigned __int64 GetSize() const {
        ULARGE_INTEGER intValue;
        intValue.LowPart = internalData.nFileSizeLow;
        intValue.HighPart = internalData.nFileSizeHigh;
        return intValue.QuadPart;
    };
    std::wstring GetFolderPath() const {
        return rootPath;
    };
    std::wstring GetFileName() const {
        return internalData.cFileName;
    };
    std::wstring GetFullFileName() const {
        return rootPath + L"\\" + internalData.cFileName;
    };
    std::wstring GetShortFileName() const {
        return internalData.cAlternateFileName;
    };
    FILETIME GetCreationTime() const {
        return internalData.ftCreationTime;
    };
    FILETIME GetLastAccessTime() const {
        return internalData.ftLastAccessTime;
    };
    FILETIME GetLastWriteTime() const {
        return internalData.ftLastWriteTime;
    };
};

class EnumerationMethod : public boost::noncopyable {
protected:
    WIN32_FIND_DATAW currentData;
    HANDLE hFind;
    std::wstring currentDirectory;
    EnumerationMethod() : hFind(INVALID_HANDLE_VALUE) {};
    void IncrementCurrentDirectory() {
        if (hFind == INVALID_HANDLE_VALUE) return;
        BOOL success =
            FindNextFile(hFind, &currentData);
        if (success)
            return;
        DWORD error = GetLastError();
        if (error == ERROR_NO_MORE_FILES) {
            FindClose(hFind);
            hFind = INVALID_HANDLE_VALUE;
        } else {
            WindowsApiException::Throw(error);
        }
    };
    virtual ~EnumerationMethod() {
        if (hFind != INVALID_HANDLE_VALUE)
            FindClose(hFind);
    };
public:
    bool equal(const EnumerationMethod& other) const {
        if (this == &other)
            return true;
        return hFind == other.hFind;
    };
    Win32FindData dereference() {
        return Win32FindData(currentDirectory, currentData);
    };
};

class NonRecursiveEnumeration : public EnumerationMethod
{
public:
    NonRecursiveEnumeration() {};
    NonRecursiveEnumeration(const std::wstring& pathSpec) {
        std::wstring::const_iterator lastSlash =
            std::find(pathSpec.rbegin(), pathSpec.rend(), L'\\').base();
        if (lastSlash != pathSpec.begin())
            currentDirectory.assign(pathSpec.begin(), lastSlash-1);
        hFind = FindFirstFileW(pathSpec.c_str(), &currentData);
        if (hFind == INVALID_HANDLE_VALUE)
            WindowsApiException::ThrowFromLastError();
        while (hFind != INVALID_HANDLE_VALUE && (!wcscmp(currentData.cFileName, L".") || !wcscmp(currentData.cFileName, L".."))) {
            IncrementCurrentDirectory();
        }
    };
    void increment() {
        IncrementCurrentDirectory();
    };
};

class RecursiveEnumeration : public EnumerationMethod
{
    std::wstring fileSpec;
    std::list<std::wstring> futureDirectories;
    std::list<std::wstring>::iterator directoryInsertLocation;
    void ShiftToNextDirectory() {
        if (futureDirectories.empty()) {
            hFind = INVALID_HANDLE_VALUE;
            return;
        }
        //Get the next directory
        currentDirectory = futureDirectories.front();
        futureDirectories.pop_front();
        directoryInsertLocation = futureDirectories.begin();
        std::wstring pathSpec(currentDirectory);
        if (!pathSpec.empty())
            pathSpec.push_back(L'\\');
        pathSpec.append(fileSpec);

        hFind = FindFirstFileW(pathSpec.c_str(), &currentData);
        if (hFind == INVALID_HANDLE_VALUE)
            WindowsApiException::ThrowFromLastError();
        while (!wcscmp(currentData.cFileName, L".") || !wcscmp(currentData.cFileName, L"..")) {
            IncrementCurrentDirectory();
        }
    };
    void IncrementAndShift() {
        if (hFind != INVALID_HANDLE_VALUE && (currentData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
            directoryInsertLocation = futureDirectories.insert(directoryInsertLocation,
                currentDirectory + L"\\" + currentData.cFileName);
            directoryInsertLocation++;
        }
        IncrementCurrentDirectory();
        if (hFind == INVALID_HANDLE_VALUE)
            ShiftToNextDirectory();
    };
public:
    RecursiveEnumeration() {};
    RecursiveEnumeration(const std::wstring& pathSpec) {
        std::wstring::const_iterator lastSlash =
            std::find(pathSpec.rbegin(), pathSpec.rend(), L'\\').base();
        if (lastSlash != pathSpec.begin()) {
            futureDirectories.push_back(std::wstring(pathSpec.begin(), lastSlash-1));
            fileSpec.assign(lastSlash, pathSpec.end());
        } else {
            futureDirectories.push_back(std::wstring());
            fileSpec = pathSpec;
        }
        ShiftToNextDirectory();
    };
    void increment() {
        do {
            IncrementAndShift();
        } while (!PathMatchSpecW(currentData.cFileName, fileSpec.c_str()));
    };
};

struct AllResults
{
    bool operator()(const Win32FindData&) {
        return true;
    };
}; 

struct FilesOnly
{
    bool operator()(const Win32FindData& arg) {
        return arg.IsFile();
    };
};

template <typename Filter_T = AllResults, typename Recurse_T = NonRecursiveEnumeration>
class DirectoryIterator : 
    public boost::iterator_facade<DirectoryIterator<Filter_T, Recurse_T>, Win32FindData, std::input_iterator_tag, Win32FindData>
{
    friend class boost::iterator_core_access;
    boost::shared_ptr<Recurse_T> impl;
    Filter_T filter;
    void increment() {
        do {
            impl->increment();
        } while (! filter(impl->dereference()));
    };
    bool equal(const DirectoryIterator& other) const {
        return impl->equal(*other.impl);
    };
    Win32FindData dereference() const {
        return impl->dereference();
    };
public:
    DirectoryIterator(Filter_T functor = Filter_T()) :
        impl(boost::make_shared<Recurse_T>()),
        filter(functor) {
    };
    explicit DirectoryIterator(const std::wstring& pathSpec, Filter_T functor = Filter_T()) :
        impl(boost::make_shared<Recurse_T>(pathSpec)),
        filter(functor) {
    };
};

}}

person Billy ONeal    schedule 08.04.2010    source sumber


Jawaban (1)


Saya tidak mengerti mengapa iterator Anda tidak menghasilkan objek yang berisi data WIN32_FIND_DATAW saat didereferensi alih-alih merujuk ke objek proxy ini yang diubah saat iterator bertambah.

Iterator ditandai seperti pada input iterator, biarkan pengguna memutuskan apakah mereka ingin salinan informasi file yang dirujuknya atau tidak. Dereferensi iterator harus mengembalikan objek yang dapat disalin dengan antarmuka serupa ke kelas FileData Anda yang berisi data anggota yang sesuai di kelas tersebut sehingga dapat disalin seperlunya untuk menjaga identitas objek. Kelas ini tidak memerlukan parameter templat yang menunjukkan bagaimana operasi pencarian harus dilakukan, karena objek jika kelas itu tidak benar-benar ada hubungannya dengan pencarian, kecuali operasi pencarian dapat menghasilkannya - objek ini hanya berisi informasi tentang file.

Kemudian ketika iterator didereferensi, ia dapat mengembalikan objek bertipe tersebut.

Apa yang dilakukan iterator ketika diinkremen tidak perlu ada hubungannya dengan objek yang dikembalikannya ketika didereferensi (walaupun beberapa data tersebut terlihat sangat mirip dengan objek yang dikembalikan dan/atau digunakan dalam operasi pertambahan ).

Saya mungkin akan mengganti nama kelas FileData<> yang Anda miliki menjadi FileFindDataInternal<>, karena ini benar-benar hanya digunakan sebagai bagian dari operasi internal iterator.

Itu akan membebaskan nama FileData untuk digunakan pada kelas yang membungkus informasi yang diminati pengguna, dan yang harus dapat disalin oleh pengguna.

person Michael Burr    schedule 08.04.2010
comment
mengapa iterator Anda tidak menghasilkan objek yang berisi data WIN32_FIND_DATAW saat didereferensi ‹-- Karena setiap dereferensi memerlukan penyalinan seluruh struktur WIN32_FIND_DATA 580 byte, ditambah string jalur root. Namun jika menurut Anda itu adalah pengorbanan yang tidak dapat diterima, maka saya akan memfaktorkan ulang proxy tersebut. - person Billy ONeal; 09.04.2010
comment
@Michael Burr: Mengedit modifikasi pada pertanyaan saya. Apakah itu yang Anda maksud? - person Billy ONeal; 09.04.2010