Implementing RAII on a folder iteration
Asked Answered
T

2

8

I wrote this code in order to loop recursively through a folder tree and list files with their size in bytes.

Since I am using winapi and there is a Handle that should be opened and closed, I should implement RAII on this code, the problem is the examples given in online forums (not to mention that I am not a native English speaker) and many books including Effective C++ are way over the head of a person who isn't finding any place to get experience.

Anyone kind enough to point me at least?

#include <iostream>
#include <string>
#include <windows.h>

void findFiles(std::string & spath) {

  size_t i = 1;
  WIN32_FIND_DATA FindFileData;

  std::string sourcepath = spath + std::string("\\*.*");

  HANDLE hFind = FindFirstFile(sourcepath.c_str(), & FindFileData);

  if (hFind != INVALID_HANDLE_VALUE)
    do {
      std::string fullpath = std::string(spath) + std::string("\\") + std::string(FindFileData.cFileName);

      if ( * (fullpath.rbegin()) == '.')
        continue;
      else
      if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        findFiles(fullpath);
      else

        std::cout << i++ << "-" << FindFileData.cFileName << " " << (FindFileData.nFileSizeHigh *(MAXWORD + 1)) + FindFileData.nFileSizeLow << std::endl;

    } while (FindNextFile(hFind, & FindFileData));

  FindClose(hFind);

}

int main(int argc, char ** argv) {

  std::string spath(argv[1]);

  findFiles(spath);

}
Tailspin answered 27/6, 2020 at 21:25 Comment(2)
Have you tried using filesystem of the stl?Mittiemittimus
I know about not reinventing the wheel, but I am taking the longer road to learn, Notepad as IDE, small compiler, implementing vectors, ....Tailspin
G
2

The simplest pattern would be:

struct HandleWrapper {
    HANDLE hFind = NULL;
    ~HandleWrapper() { if (hFind) ::FindClose(hFind); }
};

You could use that verbatim in your code, unmodified:

void findFiles(std::string const spath) {
    size_t i = 1;
    WIN32_FIND_DATA FindFileData;
    std::string sourcepath = spath + std::string("\\*.*");

    struct HandleWrapper {
        HANDLE hFind = INVALID_HANDLE_VALUE;
        ~HandleWrapper() { if (hFind != INVALID_HANDLE_VALUE) ::FindClose(hFind); }
    } wrapper;

    wrapper.hFind = FindFirstFile(sourcepath.c_str(), &FindFileData);
    if (wrapper.hFind != INVALID_HANDLE_VALUE) {
        do {
            std::string fullpath = std::string(spath) + std::string("\\") +
                std::string(FindFileData.cFileName);
            if (*(fullpath.rbegin()) == '.')
                continue;
            else if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                findFiles(fullpath);
            else
                std::cout << i++ << "-" << FindFileData.cFileName << " "
                    << (FindFileData.nFileSizeHigh * (MAXWORD + 1)) +
                    FindFileData.nFileSizeLow
                    << std::endl;
        } while (FindNextFile(wrapper.hFind, &FindFileData));
    }
    // no more CloseHandle here
}

Now the RAII makes sure that FindClose is always called even in the case of exceptions.


IDIOMS

It's not customary to keep writing RAII wrappers all the time. In fact, for WIN32 API Handles you can often use existing building blocks:

std::unique_ptr<void, decltype(&::FindClose)>
    hFind(FindFirstFile(sourcepath.c_str(), &FindFileData), ::FindClose);

This works out because HANDLE is actaully a VOID pointer. When you access the handle, use hFind.get().

More encapsulation?

You might actually want to encapsulate more of your finder into a struct. I leave this as an exorcism for the reader

BUT WAIT. The Filesystem Library

The Filesystem Library already has you covered, and it's portable:

Live On Coliru

#include <iostream>
#include <string>
#include <vector>
#include <filesystem>

namespace fs = std::filesystem;

int main(int argc, char** argv) {
    size_t i = 1;
    for (auto arg : std::vector<std::string>(argv + 1, argv + argc))
    {
        auto flags = fs::directory_options::skip_permission_denied;
        for (auto de : fs::recursive_directory_iterator(arg, flags)) {
            if (de.status().type() == fs::file_type::regular)
                std::cout << i++ << "-" << de.path() << " " << de.file_size() << "\n";
        }
    }
}

No more weird size calculations (what was that about? are we still in 1980?) and no more manual resource management.

Ah, and you actually get more robustness for free, because we can just pass a flag to ignore permission errors during traversal. If you want, you can also specify what to do with directory links.

Here's the output from the live demo (the command line arguments are . /usr/local/include/boost/archive):

1-"./main.cpp" 545
2-"./a.out" 21672
3-"/usr/local/include/boost/archive/iterators/unescape.hpp" 2343
4-"/usr/local/include/boost/archive/iterators/dataflow.hpp" 2707
5-"/usr/local/include/boost/archive/iterators/xml_unescape.hpp" 3684
6-"/usr/local/include/boost/archive/iterators/xml_escape.hpp" 2903
7-"/usr/local/include/boost/archive/iterators/istream_iterator.hpp" 2525
8-"/usr/local/include/boost/archive/iterators/mb_from_wchar.hpp" 3804
9-"/usr/local/include/boost/archive/iterators/base64_exception.hpp" 1879
10-"/usr/local/include/boost/archive/iterators/wchar_from_mb.hpp" 5645
11-"/usr/local/include/boost/archive/iterators/insert_linebreaks.hpp" 2671
12-"/usr/local/include/boost/archive/iterators/escape.hpp" 3086
13-"/usr/local/include/boost/archive/iterators/xml_unescape_exception.hpp" 1312
14-"/usr/local/include/boost/archive/iterators/transform_width.hpp" 5546
15-"/usr/local/include/boost/archive/iterators/head_iterator.hpp" 2084
16-"/usr/local/include/boost/archive/iterators/dataflow_exception.hpp" 2267
17-"/usr/local/include/boost/archive/iterators/ostream_iterator.hpp" 2396
18-"/usr/local/include/boost/archive/iterators/remove_whitespace.hpp" 4552
19-"/usr/local/include/boost/archive/iterators/base64_from_binary.hpp" 3193
20-"/usr/local/include/boost/archive/iterators/binary_from_base64.hpp" 3842
21-"/usr/local/include/boost/archive/polymorphic_text_wiarchive.hpp" 1615
22-"/usr/local/include/boost/archive/text_iarchive.hpp" 3481
23-"/usr/local/include/boost/archive/binary_iarchive_impl.hpp" 2989
24-"/usr/local/include/boost/archive/wcslen.hpp" 1321
25-"/usr/local/include/boost/archive/polymorphic_xml_iarchive.hpp" 1466
26-"/usr/local/include/boost/archive/archive_exception.hpp" 4137
27-"/usr/local/include/boost/archive/basic_text_oprimitive.hpp" 7277
28-"/usr/local/include/boost/archive/polymorphic_text_iarchive.hpp" 1477
29-"/usr/local/include/boost/archive/binary_oarchive.hpp" 1970
30-"/usr/local/include/boost/archive/basic_binary_iprimitive.hpp" 6445
31-"/usr/local/include/boost/archive/polymorphic_text_oarchive.hpp" 1340
32-"/usr/local/include/boost/archive/polymorphic_oarchive.hpp" 4779
33-"/usr/local/include/boost/archive/text_woarchive.hpp" 4473
34-"/usr/local/include/boost/archive/impl/basic_text_oprimitive.ipp" 2949
35-"/usr/local/include/boost/archive/impl/basic_text_iarchive.ipp" 2516
36-"/usr/local/include/boost/archive/impl/xml_wiarchive_impl.ipp" 5256
37-"/usr/local/include/boost/archive/impl/xml_iarchive_impl.ipp" 5703
38-"/usr/local/include/boost/archive/impl/text_oarchive_impl.ipp" 3086
39-"/usr/local/include/boost/archive/impl/basic_binary_oprimitive.ipp" 3735
40-"/usr/local/include/boost/archive/impl/text_woarchive_impl.ipp" 2155
41-"/usr/local/include/boost/archive/impl/basic_binary_iarchive.ipp" 4325
42-"/usr/local/include/boost/archive/impl/basic_xml_oarchive.ipp" 7673
43-"/usr/local/include/boost/archive/impl/basic_binary_iprimitive.ipp" 5097
44-"/usr/local/include/boost/archive/impl/basic_xml_iarchive.ipp" 3565
45-"/usr/local/include/boost/archive/impl/basic_text_iprimitive.ipp" 3510
46-"/usr/local/include/boost/archive/impl/xml_oarchive_impl.ipp" 4082
47-"/usr/local/include/boost/archive/impl/text_iarchive_impl.ipp" 3298
48-"/usr/local/include/boost/archive/impl/basic_binary_oarchive.ipp" 1282
49-"/usr/local/include/boost/archive/impl/basic_xml_grammar.hpp" 5159
50-"/usr/local/include/boost/archive/impl/archive_serializer_map.ipp" 2347
51-"/usr/local/include/boost/archive/impl/xml_woarchive_impl.ipp" 4663
52-"/usr/local/include/boost/archive/impl/text_wiarchive_impl.ipp" 2881
53-"/usr/local/include/boost/archive/impl/basic_text_oarchive.ipp" 1653
54-"/usr/local/include/boost/archive/tmpdir.hpp" 1227
55-"/usr/local/include/boost/archive/basic_archive.hpp" 9404
56-"/usr/local/include/boost/archive/xml_iarchive.hpp" 3644
57-"/usr/local/include/boost/archive/polymorphic_iarchive.hpp" 5137
58-"/usr/local/include/boost/archive/basic_xml_oarchive.hpp" 4319
59-"/usr/local/include/boost/archive/binary_oarchive_impl.hpp" 3018
60-"/usr/local/include/boost/archive/xml_oarchive.hpp" 3652
61-"/usr/local/include/boost/archive/polymorphic_xml_woarchive.hpp" 1467
62-"/usr/local/include/boost/archive/dinkumware.hpp" 5395
63-"/usr/local/include/boost/archive/detail/basic_iarchive.hpp" 3576
64-"/usr/local/include/boost/archive/detail/basic_pointer_oserializer.hpp" 1942
65-"/usr/local/include/boost/archive/detail/auto_link_warchive.hpp" 1552
66-"/usr/local/include/boost/archive/detail/utf8_codecvt_facet.hpp" 966
67-"/usr/local/include/boost/archive/detail/common_iarchive.hpp" 2469
68-"/usr/local/include/boost/archive/detail/oserializer.hpp" 17569
69-"/usr/local/include/boost/archive/detail/basic_oarchive.hpp" 3269
70-"/usr/local/include/boost/archive/detail/polymorphic_oarchive_route.hpp" 6318
71-"/usr/local/include/boost/archive/detail/abi_suffix.hpp" 545
72-"/usr/local/include/boost/archive/detail/polymorphic_iarchive_route.hpp" 6647
73-"/usr/local/include/boost/archive/detail/register_archive.hpp" 3695
74-"/usr/local/include/boost/archive/detail/common_oarchive.hpp" 2436
75-"/usr/local/include/boost/archive/detail/basic_oserializer.hpp" 2595
76-"/usr/local/include/boost/archive/detail/basic_serializer_map.hpp" 1925
77-"/usr/local/include/boost/archive/detail/basic_serializer.hpp" 2159
78-"/usr/local/include/boost/archive/detail/abi_prefix.hpp" 593
79-"/usr/local/include/boost/archive/detail/basic_pointer_iserializer.hpp" 2049
80-"/usr/local/include/boost/archive/detail/iserializer.hpp" 20947
81-"/usr/local/include/boost/archive/detail/archive_serializer_map.hpp" 1714
82-"/usr/local/include/boost/archive/detail/basic_iserializer.hpp" 2704
83-"/usr/local/include/boost/archive/detail/check.hpp" 5397
84-"/usr/local/include/boost/archive/detail/auto_link_archive.hpp" 1689
85-"/usr/local/include/boost/archive/detail/decl.hpp" 1744
86-"/usr/local/include/boost/archive/detail/interface_oarchive.hpp" 2504
87-"/usr/local/include/boost/archive/detail/helper_collection.hpp" 2844
88-"/usr/local/include/boost/archive/detail/interface_iarchive.hpp" 2534
89-"/usr/local/include/boost/archive/polymorphic_xml_wiarchive.hpp" 1469
90-"/usr/local/include/boost/archive/polymorphic_binary_iarchive.hpp" 1499
91-"/usr/local/include/boost/archive/basic_text_oarchive.hpp" 3400
92-"/usr/local/include/boost/archive/xml_archive_exception.hpp" 1786
93-"/usr/local/include/boost/archive/basic_binary_iarchive.hpp" 7563
94-"/usr/local/include/boost/archive/binary_wiarchive.hpp" 1704
95-"/usr/local/include/boost/archive/binary_iarchive.hpp" 1973
96-"/usr/local/include/boost/archive/text_wiarchive.hpp" 3630
97-"/usr/local/include/boost/archive/basic_text_iprimitive.hpp" 4015
98-"/usr/local/include/boost/archive/basic_binary_oarchive.hpp" 6245
99-"/usr/local/include/boost/archive/basic_binary_oprimitive.hpp" 6233
100-"/usr/local/include/boost/archive/basic_streambuf_locale_saver.hpp" 2853
101-"/usr/local/include/boost/archive/polymorphic_text_woarchive.hpp" 1478
102-"/usr/local/include/boost/archive/text_oarchive.hpp" 3511
103-"/usr/local/include/boost/archive/basic_text_iarchive.hpp" 3001
104-"/usr/local/include/boost/archive/basic_xml_iarchive.hpp" 4003
105-"/usr/local/include/boost/archive/xml_woarchive.hpp" 3780
106-"/usr/local/include/boost/archive/codecvt_null.hpp" 3100
107-"/usr/local/include/boost/archive/xml_wiarchive.hpp" 3879
108-"/usr/local/include/boost/archive/binary_woarchive.hpp" 1890
109-"/usr/local/include/boost/archive/polymorphic_binary_oarchive.hpp" 1362
110-"/usr/local/include/boost/archive/polymorphic_xml_oarchive.hpp" 1328
111-"/usr/local/include/boost/archive/basic_xml_archive.hpp" 1644
112-"/usr/local/include/boost/archive/add_facet.hpp" 1733
Grenville answered 27/6, 2020 at 22:26 Comment(1)
The Windows Implementation Libraries provide RAII wrappers for effectively all native Windows handle types. And while the C++ filesystem library is indeed portable, the usage in this proposed answer isn't. Windows' filesystem API is based on UTF-16, and you need to construct the recursive_directory_iterator from a wide character string.Faraday
M
0

In the context of the winapi tag and C APIs in general, one way that I find quite unintrusive yet RAII-safe is to fence the acquisition and release of resources in a boilerplate wrapper, where the actual definitions of the actions are deferred to lambdas at the point of instantiation.

#include <functional>

class XWrap
{
  std::function<void()> fnOut;

public:
  XWrap(const std::function<void()> &fnIn,
        const std::function<void()> &fnOut) : fnOut(fnOut)
  { if(fnIn) fnIn(); }

  ~XWrap()
  { if(fnOut) fnOut(); }
};

Then, the original code can be written as follows, where the body of the code that uses hFind and FindFileData remains essentially unchanged.

void findFiles(const std::string &spath)
{
  size_t i = 1;
  std::string sourcepath = spath + std::string("\\*.*");

  HANDLE hFind;
  WIN32_FIND_DATA FindFileData;
  XWrap wrap(
    [&]() { hFind = ::FindFirstFile(sourcepath.c_str(), &FindFileData); },
    [&]() { if(hFind != INVALID_HANDLE_VALUE) { ::FindClose(hFind); /* plus maybe hFind = INVALID_HANDLE_VALUE; */ } }
  );
  if(hFind == INVALID_HANDLE_VALUE) return;

  do {
    std::string fullpath = std::string(spath) + std::string("\\") + std::string(FindFileData.cFileName);

    if (fullpath.back() == '.')
      continue;

    if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
      findFiles(fullpath);
    else
      std::cout << i++ << "-" << FindFileData.cFileName << " " << (FindFileData.nFileSizeHigh *(MAXWORD + 1)) + FindFileData.nFileSizeLow << std::endl;

  } while (FindNextFile(hFind, &FindFileData));
}

Same pattern can be used for any objects with acquire/release semantics (handles, pointers etc) .

Mendicity answered 28/6, 2020 at 4:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.