Основные концепции со ссылками на std :: string, std :: regex и boost :: filesystem

Ниже я создаю как сломанный код, так и его фиксированную версию. Проблема в том, что я не могу полностью объяснить себе, почему первое не работает, а второе работает. Мне, очевидно, нужно пересмотреть некоторые очень базовые концепции языка C ++: не могли бы вы указать, что мне следует проверить, и, возможно, также объяснить, почему я получаю результаты, которые получаю с неработающим кодом.

В каталоге '../docs/', упомянутом в коде, я просто использовал команду 'touch' в Linux для создания нескольких файлов doc ...... html различной длины.

#include <iostream>
#include <regex>
#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

int main() {
        fs::path p("../docs/");

        for (auto& dir_it : fs::directory_iterator(p)) {
                std::regex re = std::regex("^(doc[a-z]+)\\.html$");
                std::smatch matches;
                // BROKEN HERE:
                if (std::regex_match(dir_it.path().filename().string(), matches, re)) {
                        std::cout << "\t\t" <<dir_it.path().filename().string();
                        std::cout << "\t\t\t" << matches[1] << std::endl;
                }
        }

        return 0;
}

Производит:

        documentati.html                        ati
        documentationt.html                     �}:ationt
        document.html                   document
        documenta.html                  documenta
        docume.html                     docume
        documentat.html                 documentat
        docum.html                      docum
        documentatio.html                       ��:atio
        documen.html                    documen
        docu.html                       docu
        documentation.html                      ��:ation
        documaeuaoeu.html                       ��:aoeu

Примечание 1. Указанная выше ошибка возникает, если имена файлов превышают определенную длину. Я понимаю это только потому, что объект std :: string сам меняет размер.

Примечание 2. Приведенный выше код очень похож на код, используемый в следующем вопросе, но с boost :: regex_match вместо std :: regex_match: Могу ли я использовать маску для итерации файлов в каталоге с Boost?
Раньше это работало для меня и раньше, но теперь я использую GCC 5.4 вместо GCC 4.6, std :: regex вместо boost :: regex, C ++ 11 и гораздо более новую версию boost :: filesystem. Какое изменение актуально, из-за которого рабочий код сломался?

Фиксированный:

#include <iostream>
#include <regex>
#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

int main() {
        fs::path p("../docs/");

        for (auto& dir_it : fs::directory_iterator(p)) {
                std::regex re = std::regex("^(doc[a-z]+)\\.html$");
                std::smatch matches;
                std::string p = dir_it.path().filename().string();
                if (std::regex_match(p, matches, re)) {
                        std::cout << "\t\t" <<dir_it.path().filename().string();
                        std::cout << "\t\t\t" << matches[1] << std::endl;
                }
        }

        return 0;
}

производит:

        documentati.html                        documentati
        documentationt.html                     documentationt
        document.html                   document
        documenta.html                  documenta
        docume.html                     docume
        documentat.html                 documentat
        docum.html                      docum
        documentatio.html                       documentatio
        documen.html                    documen
        docu.html                       docu
        documentation.html                      documentation
        documaeuaoeu.html                       documaeuaoeu

При использовании boost 1.62.0-r1 и gcc (Gentoo 5.4.0-r3) документация boost :: filesystem не дает четких указаний относительно того, какой путь (). Filename (). String () возвращает: http://www.boost.org/doc/libs/1_62_0/libs/filesystem/doc/reference.html#path-filename
Похоже, что это зависит от:
Почему boost :: filesystem :: path :: string () возвращается по значению в Windows и по ссылке в POSIX?


person augustin    schedule 24.07.2017    source источник


Ответы (1)


std::smatch не сохраняет копию символов, составляющих совпадение, а только пары итераторов в исходной строке, в которой был выполнен поиск.

Первый фрагмент передает временную строку в std::regex_match. К тому времени, когда вы приступите к изучению matches, эта строка исчезнет, ​​и все итераторы, удерживаемые matches, будут болтаться. Затем программа демонстрирует неопределенное поведение, пытаясь их использовать.

Во втором фрагменте строка, переданная в std::regex_match, все еще жива к моменту использования matches, так что все в порядке.


Примечание 1. То, что проблема проявляется по-разному в зависимости от длины имени файла, вероятно, связано с небольшой оптимизацией строки. Для std::string реализаций обычно резервируется небольшой буфер в экземпляре класса и хранятся там короткие строки; тогда как более длинные строки размещаются в куче. Возможно, что пространство стека, ранее занятое временной строкой, все еще не повреждено, в то время как пространство кучи было повторно использовано для других распределений. Программа демонстрирует неопределенное поведение в любом случае - «кажется, работает» - одно из возможных проявлений UB.


Примечание 2. Не имеет большого значения, возвращается ли path::string() по значению или по ссылке. path::filename() возвращается по значению, так что dir_it.path().filename().string() в любом случае будет уничтожен слишком рано, будь то член временного dir_it.path().filename() объекта или произведенный на лету string().

person Igor Tandetnik    schedule 24.07.2017
comment
Ой! Теперь, когда я это вижу, это кажется очевидным. +1. Спасибо за ваше время. - person augustin; 25.07.2017