2010-04-07 9 views
5

Parece que tuve que poner bastante código aquí. Me pregunto si es una práctica mal diseño de dejar esta totalmente en un fichero de cabecera de esta manera:¿Esto es demasiado código para una biblioteca solo encabezado?

#include <list> 
#include <string> 
#include <boost/noncopyable.hpp> 
#include <boost/make_shared.hpp> 
#include <boost/iterator/iterator_facade.hpp> 
#include <Windows.h> 
#include "../Exception.hpp" 

namespace WindowsAPI { namespace FileSystem { 

class NonRecursiveEnumeration; 
class RecursiveEnumeration; 
struct AllResults; 
struct FilesOnly; 

template <typename Filter_T = AllResults, typename Recurse_T = NonRecursiveEnumeration> 
class DirectoryIterator; 

template <typename Recurse_T> 
struct FileData; 

class NonRecursiveEnumeration : public boost::noncopyable 
{ 
    WIN32_FIND_DATAW currentData; 
    HANDLE hFind; 
    std::wstring root; 
public: 
    NonRecursiveEnumeration() : hFind(INVALID_HANDLE_VALUE) { 
    }; 
    NonRecursiveEnumeration(const std::wstring& pathSpec) { 
     std::wstring::const_iterator lastSlash = 
      std::find(pathSpec.rbegin(), pathSpec.rend(), L'\\').base(); 
     if (lastSlash != pathSpec.end()) 
      root.assign(pathSpec.begin(), lastSlash); 
     hFind = FindFirstFileW(pathSpec.c_str(), &currentData); 
     if (hFind == INVALID_HANDLE_VALUE) 
      WindowsApiException::ThrowFromLastError(); 
     while (!wcscmp(currentData.cFileName, L".") || !wcscmp(currentData.cFileName, L"..")) { 
      increment(); 
     } 
    }; 
    void increment() { 
     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); 
     } 
    }; 
    ~NonRecursiveEnumeration() { 
     if (hFind != INVALID_HANDLE_VALUE) 
      FindClose(hFind); 
    }; 
    bool equal(const NonRecursiveEnumeration& other) const { 
     if (this == &other) 
      return true; 
     return hFind == other.hFind; 
    }; 
    const std::wstring& GetPathRoot() const { 
     return root; 
    }; 
    const WIN32_FIND_DATAW& GetCurrentFindData() const { 
     return currentData; 
    }; 
}; 

//Not implemented yet 
class RecursiveEnumeration : public boost::noncopyable 
{ 
}; 

template <typename Recurse_T> 
struct FileData //Serves as a proxy to the WIN32_FIND_DATA struture inside the iterator. 
{ 
    const Recurse_T* impl; 
    template <typename Filter_T, typename Recurse_T> 
    FileData(const DirectoryIterator<Filter_T, Recurse_T>* parent) : impl(parent->impl.get()) {}; 
    DWORD GetAttributes() const { 
     return impl->GetCurrentFindData().dwFileAttributes; 
    }; 
    bool IsDirectory() const { 
     return (GetAttributes() & FILE_ATTRIBUTE_DIRECTORY) != 0; 
    }; 
    bool IsFile() const { 
     return !IsDirectory(); 
    }; 
    bool IsArchive() const { 
     return (GetAttributes() & FILE_ATTRIBUTE_ARCHIVE) != 0; 
    }; 
    bool IsReadOnly() const { 
     return (GetAttributes() & FILE_ATTRIBUTE_READONLY) != 0; 
    }; 
    unsigned __int64 GetSize() const { 
     ULARGE_INTEGER intValue; 
     intValue.LowPart = impl.GetCurrentFindData().nFileSizeLow; 
     intValue.HighPart = impl.GetCurrentFindData().nFileSizeHigh; 
     return intValue.QuadPart; 
    }; 
    std::wstring GetFolderPath() const { 
     return impl->GetPathRoot(); 
    }; 
    std::wstring GetFileName() const { 
     return impl->GetCurrentFindData().cFileName; 
    }; 
    std::wstring GetFullFileName() const { 
     return GetFolderPath() + GetFileName(); 
    }; 
    std::wstring GetShortFileName() const { 
     return impl->GetCurrentFindData().cAlternateFileName; 
    }; 
    FILETIME GetCreationTime() const { 
     return impl->GetCurrentFindData().ftCreationTime; 
    }; 
    FILETIME GetLastAccessTime() const { 
     return impl->GetCurrentFindData().ftLastAccessTime; 
    }; 
    FILETIME GetLastWriteTime() const { 
     return impl->GetCurrentFindData().ftLastWriteTime; 
    }; 
}; 

struct AllResults 
{ 
    template <typename Recurse_T> 
    bool operator()(const FileData<Recurse_T>&) { 
     return true; 
    }; 
}; 

struct FilesOnly 
{ 
    template <typename Recurse_T> 
    bool operator()(const FileData<Recurse_T>& arg) { 
     return arg.IsFile(); 
    }; 
}; 

#pragma warning(push) 
#pragma warning(disable: 4355) 
template <typename Filter_T, typename Recurse_T> 
class DirectoryIterator : public boost::iterator_facade<DirectoryIterator<Filter_T>, const FileData<Recurse_T>, std::input_iterator_tag> 
{ 
    friend class boost::iterator_core_access; 
    boost::shared_ptr<Recurse_T> impl; 
    FileData<Recurse_T> derefData; 
    Filter_T filter; 
    void increment() { 
     do { 
      impl->increment(); 
     } while (! filter(derefData)); 
    }; 
    bool equal(const DirectoryIterator& other) const { 
     return impl->equal(*other.impl); 
    }; 
    const FileData<Recurse_T>& dereference() const { 
     return derefData; 
    }; 
public: 
    typedef FileData<Recurse_T> DataType; 
    friend struct DataType; 
    DirectoryIterator(Filter_T functor = Filter_T()) : 
     impl(boost::make_shared<Recurse_T>()), 
     derefData(this), 
     filter(functor) { 
    }; 
    explicit DirectoryIterator(const std::wstring& pathSpec, Filter_T functor = Filter_T()) : 
     impl(boost::make_shared<Recurse_T>(pathSpec)), 
     derefData(this), 
     filter(functor) { 
    }; 
}; 
#pragma warning(pop) 

}} 
+4

Si solo es encabezado, creo que la mejor opción es dividirlo en varios archivos. Como "detail/DirectoryIterator.hpp", etc., por lo menos no está contenido en un solo archivo. Esto es lo que hace Boost. Por cierto, Boost tiene una biblioteca de sistema de archivos, por si acaso no lo sabía. – GManNickG

+0

@GMan: +1. Sí, boost tiene una biblioteca de sistema de archivos, pero está diseñada para compatibilidad multiplataforma que requiere abrir una lata de gusanos que no quiero (tiene su propio procesador de ruta, por ejemplo). Con esto, creo que puedo hacer que el código del cliente sea más claro y que no se preocupe por la portabilidad multiplataforma. –

+0

Es bueno ver que está obteniendo un buen uso de: http://stackoverflow.com/questions/2531874/how-might-i-wrap-the-findxfile-style-apis-to-the-stl-style-iterator -pattern-in-c –

Respuesta

9

tengo mucho más código en algunas de las minas, si eso es de consuelo. y también lo hacen todas las implementaciones de la Biblioteca Estándar de C++, Boost y Microsoft (por ejemplo, ATL).

1

En cuanto a la longitud del encabezado, puede tener tanto código como desee en sus archivos de encabezado. La compensación es la cantidad de código que debe recompilarse cada vez que se crea su programa; el código colocado en sus archivos CPP puede compilarse en archivos de objeto y enlazarse en cada compilación subsiguiente.

Sugeriría que cada una de las definiciones de método para DirectoryIteratorImpl se debe mover a un archivo .cpp. Si no está definiendo un método en línea dentro de una definición de clase, no hay razón para que se incluya en el archivo de encabezado.

Un lado no relacionado: Evite escribir inline DirectoryIteratorImpl(); - en realidad escriba sus funciones en línea en línea, o no las marque en línea. Desde el C++ FAQ Lite:

Por lo general es imperativo que la definición de la función (la parte entre el {...}) ser colocado en un archivo de cabecera. Si coloca la definición de la función en línea en un archivo .cpp , y si se llama desde algún otro archivo .cpp, obtendrá un error "no resuelto externo" del vinculador.

Si sus funciones son "demasiado grandes" para escribir en el archivo de encabezado, son demasiado grandes para alinearlas y el compilador probablemente ignorará su sugerencia en línea de todos modos.

+0

# 1. No respondiste mi pregunta Sé que el código se pone en cada archivo que usa este código, me preguntaba si eso llevaría a demasiada obstrucción del código. # 2. No puedo hacer que las funciones sean una parte literal de la clase porque la mitad de ellas depende de la definición completa de 'clase FileData 'que está disponible. ¿Por qué crees que declare todos los miembros de 'FileData' implícitamente en línea? :) –

+0

@billy Quizás el cuerpo de la pregunta debería haber especificado. Además, mi sugerencia es: nix la definición 'en línea' si no puedes declarar la función en línea. – meagar

+0

@meagar: Creo que es algo así como implícito. Las implicaciones de hacer una función en línea deberían ser inherentemente obvias. Estaba preguntando si esa es una buena idea. La única razón por la que alguna vez sería una mala idea sería provocar demasiada obstrucción del código. –

6

La única parte que me parece abierta a muchas preguntas sería la implementación de las funciones en DirectoryIteratorImpl. No es una plantilla, por lo que realmente no tiene que estar en un encabezado, y tiene un par de rutinas algo más largas (el constructor "real" y el incremento).

El resto son plantillas o bien están compuestas de funciones tan triviales que desearía que estuvieran en línea en cualquier caso (por ejemplo, los miembros de FileData). Esos terminarán en un encabezado en cualquier caso.

+0

Límite diario de votos alcanzado; vuelve en 4 horas. <- ¡Maldición! Voy a votar en 4 horas. –

+0

@Billy: Ja, seguro que te quedas sin votos. Hizo +1 por ti. – GManNickG

+0

@GMan: Acabo de salir desde las 8 esta mañana. * Bill se escapa a meta para solicitar más votos. (Es una broma) –

1

Parece que está programando para Windows aquí, ¿podemos suponer que está utilizando Visual Studio?

De todos modos, no creo que haya algo como demasiado código en los encabezados.

Es una cuestión de compensaciones en su mayoría:

  • compilación más lenta (pero tenemos multicores y encabezados precompilados)
  • recompilación más frecuente (una vez más, multicores)
  • quizá hinchazón de código ...

El único punto que es molesto (en mi opinión) es el último ... y voy a necesitar ayuda aquí: ¿estamos seguros de que las funciones van a estar en línea, no es posible que la compilación ¿y el vinculador decide no alinearlos y transformarlos en una llamada regular?

Francamente, no me preocuparía demasiado por eso. Varias bibliotecas Boost son solo encabezado, incluso para sus partes que no son de plantilla, simplemente porque facilita la integración (no se requiere enlace).

Cuestiones relacionadas