Я пытаюсь понять, как такие вещи, как итераторы, можно использовать в С++, и мне особенно хотелось бы лучше понять std::filesystem::directory_iterator.
Я понимаю такие прямые примеры:
#include <iostream>
#include <filesystem>
#include <string>
void doSomething(std::string filename)
{
std::cout << filename;
};
int main()
{
auto iterator = std::filesystem::directory_iterator("c:/somefolder");
for (auto& i : iterator)
{
doSomething(std::filesystem::path(i.path()).filename().string());
}
}
Но большая часть устаревшего кода не создается таким замкнутым кругом. Можно ли использовать directory_iterator аналогично WinAPI FindNextFile()?
Что-то похожее на это:
std::string getNextFilename(std::string path)
{
// Notice the actual filesystem access code is neatly packed away in a replaceable function suitable for a HAL.
static auto iterator = std::filesystem::directory_iterator(path);
return std::filesystem::path(iterator.path()).filename().string();
}
int main()
{
while (std::string fileName = getNextFilename("c:/somefolder"))
{
doSomething(fileName);
}
// or
std::string fileFirst = getNextFilename("c:/somefolder");
std::string fileSecond = getNextFilename("c:/somefolder");
std::string fileLast = getNextFilename("c:/somefolder");
}
Ответьте, пожалуйста:
edit1: Уточненный заголовок.
Диапазон, основанный на цикле for, по сути, является просто синтаксическим сахаром для увеличения итератора и проверки того, достиг ли он итератора end()
(см.: en.cppreference.com/w/cpp/language/range-for), поэтому вы всегда можете вручную воспроизвести такое поведение. Однако для operator bool
нет std::string
, поэтому вам нужно структурировать getNextFilename
по-другому, чтобы иметь «конечное» условие (например: вернуть std::optional<std::string>
)
for (auto& e : range)
можно перевести как for (auto it = std::begin(range), it != std::end(range); ++it)
...
По сути, я хотел знать, возможно ли каким-то образом вернуть первый результат, а при последующих вызовах возвращать следующие результаты по одному.
@UnholySheep Похоже на то, о чем я думаю ... но как это можно реализовать на самом деле?
@Sneftel Да, я бы хотел использовать его вне цикла for с диапазоном. Он не должен перестраиваться или перезапускаться, пока его специально не попросят сделать это.
Я бы настоятельно не советовал getNextFilename
иметь static auto iterator = ...
, так как теперь у вас возникла ситуация, когда getNextFilename
можно использовать только для итерации каталога один, однажды во всей вашей программе.
Легко можно вручную увеличить итераторы (поскольку это то, что делает цикл for
на основе диапазона). Однако вам необходимо соответствующим образом настроить другой код, так как нет operator bool
вместо std::string
. Одно из возможных решений (лишь небольшое изменение исходного кода, который по-прежнему включает в себя все его проблемы) может выглядеть следующим образом (используя std::optional<std::string>
, чтобы включить возврат условия «конец»):
#include <iostream>
#include <filesystem>
#include <string>
#include <optional>
void doSomething(std::string filename)
{
std::cout << filename;
};
std::optional<std::string> getNextFilename(std::string path)
{
static auto iterator = std::filesystem::directory_iterator(path);
if (iterator != std::filesystem::directory_iterator()) {
auto filename = std::filesystem::path(iterator->path()).filename().string();
++iterator; // advance iterator to next entry in directory
return filename; // uses implicit constructor of `std::optional`
} else {
return {}; // return empty optional if we reached end of directory
}
}
int main()
{
while (auto fileName = getNextFilename("c:/somefolder"))
{
doSomething(*fileName); // dereference optional to get value
}
// or
auto fileFirst = getNextFilename("c:/somefolder");
auto fileSecond = getNextFilename("c:/somefolder");
auto fileLast = getNextFilename("c:/somefolder");
}
@Caleth Как вы правильно указали в комментарии к моему вопросу, использование static auto iterator =
требует особой осторожности, например, добавить это, чтобы убедиться, что его можно обновить:` static std::string oldPath; if (path.compare(oldPath)!= 0) { iterator = std::filesystem::directory_iterator(path); старый путь = путь; }` Не так изящно, но понятно.
Предполагая, что вы хотите иметь более или менее простую замену для FindFirstFile/FindNextFile, решение может выглядеть следующим образом:
struct HANDLE{
std::filesystem::directory_iterator it;
};
bool FindNextFile(HANDLE &handle, std::string& output) {
if (handle.it == std::filesystem::directory_iterator{}) return false;
output=handle.it->path().string();
handle.it++;
return true;
}
HANDLE FindFirstFile(std::string input, std::string& output) {
HANDLE h;
h.it=std::filesystem::directory_iterator{input};
FindNextFile(h,output);
return h;
}
или просто using HANDLE = std::filesystem::directory_iterator
@Caleth Да, ты можешь это сделать, хороший совет. Преимущество структуры в том, что ее можно сделать непрозрачной, но тогда я бы вернул указатели на дескриптор.
У вас может быть непрозрачный указатель на std::filesystem::directory_iterator
, это тип класса, как и любой другой.
@Калет Возможно. Я не уверен в этом, потому что вам нужно будет предварительно объявить std::filesystem::directory_iterator, чтобы заголовок файловой системы не попадал в модуль компиляции. И предварительное объявление стандартного класса кажется мне сложным, потому что вы не знаете их реализации.
Спасибо. Это очень полезное предложение для добавления в мой набор инструментов.
Не совсем понятно, что вы просите. Вы просто хотите использовать итератор файловой системы вне контекста цикла for? Или вы на самом деле требуете, чтобы итератор каждый раз «перестраивался» из имени файла?