Использование шаблона Proxy с итераторами C ++ - PullRequest
3 голосов
/ 09 апреля 2010

У меня написан довольно сложный итератор, который оборачивает API FindXFile на Win32. (См. предыдущий вопрос ). Чтобы избежать накладных расходов на создание объекта, который по существу дублирует работу структуры WIN32_FIND_DATAW, у меня есть прокси-объект, который просто действует как своего рода константная ссылка на один WIN32_FIND_DATAW, который объявляется внутри некопируемых внутренностей итератора. Это здорово, потому что

  1. Клиенты не платят за создание нерелевантной информации, которую они, вероятно, не будут использовать (большую часть времени люди интересуются только именами файлов), а
  2. Клиенты могут получать всю информацию, предоставляемую API FindXFile, если им нужна или требуется эта информация.

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

Я обеспокоен, если это серьезная проблема, потому что я могу вспомнить случай, когда прокси-объект не будет вести себя так, как кто-то может ожидать:

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

потому что вектор содержит только недопустимые прокси в этой точке. Вместо этого клиенты должны сделать что-то вроде:

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

Видя это, я понимаю, почему кому-то может не нравиться то, что дизайнеры стандартной библиотеки сделали с std::vector<bool>. Я все еще задаюсь вопросом: это разумный компромисс для достижения (1) и (2) выше? Если нет, есть ли способ достичь (1) и (2) без прокси?

РЕДАКТИРОВАТЬ: Обновленный код:

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

}}

1 Ответ

1 голос
/ 09 апреля 2010

Я не понимаю, почему ваш итератор не создает объект, содержащий данные WIN32_FIND_DATAW при разыменовании, вместо ссылки на этот прокси-объект, который изменяется при увеличении итератора.

Итератор помечен как входной итератор, и пользователь может решить, хочет ли он получить копию информации о файле, на которую он ссылается, или нет. Разыменование итератора должно возвращать копируемый объект с интерфейсом, аналогичным интерфейсу вашего FileData класса, который содержит соответствующие данные члена, которые в этом классе могут быть скопированы по мере необходимости для сохранения идентичности объекта. Этому классу не нужен параметр шаблона, указывающий, как должна выполняться операция поиска, поскольку объекты, если этот класс на самом деле не имеет ничего общего с поиском, за исключением того, что операция поиска может его создать - этот объект просто содержит информацию о файл.

Затем, когда разыменовывается итератор, он может вернуть объект этого типа.

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

Я бы, вероятно, переименовал класс FileData<>, который у вас есть, в что-то вроде FileFindDataInternal<>, поскольку он действительно используется только как часть внутренней операции итератора.

Это освободило бы имя FileData для использования в классе, который упаковывает информацию, интересующую пользователя, и которая должна быть скопирована пользователем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...