1 Вопрос: `std :: filesystem :: path :: operator / (/ * args * /)` не работает должным образом

вопрос создан в Wed, May 8, 2019 12:00 AM

У меня есть класс со списком инициализаторов в конструкторе, где одно из полей, которое я инициализирую, является std::filesystem::path, но, похоже, оно не инициализируется ожидаемым значением.

MyClass::MyClass(
    unsigned int deviceSerial,
    const std::string& processName
) :
    deviceSerial(deviceSerial),
    processName(processName),
    configFilePath(GetBasePath() / std::to_string(deviceSerial) / ("#" + processName + ".json"))
{
    /* Parameter checks */
}

Используя отладчик, я вижу, что GetBasePath() возвращает именно то, что я ожидаю (возвращает std::filesystem::path с правильным путем), но оператор /, похоже, не дает эффекта. Оказавшись внутри тела конструктора, я вижу, что configFilePath настроен на результат GetBasePath() без добавления дополнительной информации.

Я использую MSVS-2019, у меня установлен стандарт языка C ++ C ++ 17, а в режиме отладки все оптимизации отключены.

Я также проверил следующее в теле класса и все еще вижу path как просто результат GetBasePath(), и дополнительные элементы не добавляются.

{
    auto path = GetBasePath();             // path = "C:/Users/Me/Desktop/Devices"
    path /= std::to_string(deviceSerial);  // path = "C:/Users/Me/Desktop/Devices"
    path /= ("#" + processName + ".json"); // path = "C:/Users/Me/Desktop/Devices"
}

На небольшом примечании стороны я также попробовал вышеупомянутый тест с += вместо /=, и я все еще вижу те же результаты.

Изменить

В соответствии с просьбой ниже приведен минимальный полный и проверяемый пример.

#include <Windows.h>
#include <cstdio>
#include <filesystem>
#include <memory>
#include <string>

std::string ExpandPath(const std::string &str) {
  auto reqBufferLen = ExpandEnvironmentStrings(str.c_str(), nullptr, 0);

  if (reqBufferLen == 0) {
    throw std::system_error((int)GetLastError(), std::system_category(),
                            "ExpandEnvironmentStrings() failed.");
  }

  auto buffer = std::make_unique<char[]>(reqBufferLen);
  auto setBufferLen =
      ExpandEnvironmentStrings(str.c_str(), buffer.get(), reqBufferLen);

  if (setBufferLen != reqBufferLen - 1) {
    throw std::system_error((int)GetLastError(), std::system_category(),
                            "ExpandEnvironmentStrings() failed.");
  }

  return std::string{buffer.get(), setBufferLen};
}

int main() {
  unsigned int serial = 12345;
  std::string procName = "Bake";

  std::filesystem::path p(ExpandPath("%USERPROFILE%\\Desktop\\Devices"));
  std::printf("Path = %s\n", p.string().c_str());
  // p = C:\Users\Me\Desktop\Devices

  p /= std::to_string(serial);
  std::printf("Path = %s\n", p.string().c_str());
  // p = C:\Users\Me\Desktop\Devices

  p /= "#" + procName + ".json";
  std::printf("Path = %s\n", p.string().c_str());
  // p = C:\Users\Me\Desktop\Devices

  std::getchar();
}

I've also used this example and tested with `p.append()` and got the same result.
    
2
  1. Можете ли вы предоставить пример Minimal, Complete и Verifiable .
    2019-05-08 16: 08: 40Z
  2. Это скорее предположение, но попробуйте метод fs :: append ().
    2019-05-08 16: 21: 48Z
  3. "Использование отладчика" Это может быть вашей проблемой. То, как данные пути хранятся в классе, зависит от конкретной реализации и может скрывать от вас некоторые вещи. Вы должны убедиться, что это воспроизводимо через интерфейс класса (то есть выведите объединенный путь к консоли)
    2019-05-08 20: 46: 41Z
  4. @ rustyx Пожалуйста, см. выше редактирование для полного примера. @Mansoor Как вы и предполагали, я также протестировал path::append(), и это дает тот же результат @Frank При создании отредактированного примера я удалил все слои абстракции, а также проверил вне отладчика при сборке в режиме выпуска. Все еще без разницы.
    2019-05-09 08: 00: 54Z
  5. Извините, пропустил #includes в MCVE, теперь должен скомпилироваться, хотя M.M - хорошее место для этой ошибки:)
    2019-05-09 08: 19: 35Z
1 ответ                              1                         

Я хотел бы поблагодарить @rustyx и @Frank за их предложения, следуя этому совету, я обнаружил ошибку в способе создания начальной строки, которая передается конструктору пути (также @MM нашел точную ошибку, пока я печатал этот ответ)

Я создал функцию (которая используется в моем классе) std::string ExpandPath(const std::string& path), которая использует Windows API для расширения любых переменных среды в пути и возврата строки. Эта строка генерируется из char* и счетчика, который включает нулевой байт, поэтому при создании строки с использованием варианта конструктора std::string(char* cstr, size_t len) он включает нулевой байт в самой строке.

Поскольку я использовал отладчик для опроса переменных, он читает строки в стиле C и останавливается на нулевом байте. В моем исходном примере я также использую printf(), так как я просто предпочитаю эту функцию для вывода, но опять же это останавливает печать с нулевым байтом. Если я изменяю вывод на использование std::cout, я вижу, что выход имеет ожидаемый путь, но с дополнительным пробелом (нулевой байт печатается как пробел). Используя std::cout, я вижу, что мои пути приводят к следующему с каждым добавлением:

Path = C:\Users\Me\Desktop\Devices
Path = C:\Users\Me\Desktop\Devices \12345
Path = C:\Users\Me\Desktop\Devices \12345\#Bake.json

Резюме:

Ошибка в моем ExpandPath() где

  return std::string{buffer.get(), setBufferLen};

Должно быть

  return std::string{buffer.get(), setBufferLen - 1};
    
1
2019-05-09 08: 18: 02Z
источник размещен Вот