Подтвердить что ты не робот

Нарушение доступа при отправке 0-строкового литерала в параметр const string

В VS2015 и VS2017 это компилируется без предупреждения и генерирует нарушение доступа, которое невозможно поймать и сбой приложения. Очевидно, что int 0 молча преобразуется в нулевой указатель, который затем считается, что он указывает на строку, которая, следовательно, сбой.

#include <string>
#include <iostream>
void crash(const std::string& s) {}
int main()
{
    try
    {
        crash(0);
    }
    catch (const std::exception& ex)
    {
        // never gets here!
        std::cout << "got" << ex.what() << std::endl;
    }
}

Как я могу отловить и восстановить из такого исключения? Если я сброшу const из параметра функции, он не компилируется - так что, возможно, один из способов защитить пользователей от неправильного использования, но я потерял бы защиту, предоставляемую const, или же я? Какая наилучшая практика для написания прототипов, которые избегают этой проблемы?

4b9b3361

Ответ 1

В этом конкретном случае вы можете получить ошибку времени компиляции с помощью С++ 11 std::nullptr_t, просто добавьте следующую удаленную перегрузку:

void crash(std::nullptr_t) = delete;

Конечно, это не защитит вас от передачи нулевых (или не нулевых) указателей char *... вы нарушаете предварительное условие конструктора std::string, приводящее к поведению undefined; это по определению невосстанавливается.

В противном случае, если вам действительно нужно поймать эти ошибки во время выполнения, возможно, восстановимым образом, вы можете написать перегрузку const char*, которая выдает, если задана нулевая указатель, или вызывает в противном случае версию std::string const&.

Если ваша реальная функция принимает более нескольких строковых аргументов, и перегрузка всех возможных комбинаций кажется невозможной, вы можете приступить к написанию шаблона функции, выполняя все проверки над выведенными типами ex-post.

Ответ 2

namespace safer {
  template<class CharT,
    class Traits = ::std::char_traits<CharT>,
    class Allocator = ::std::allocator<CharT>,
    class Base = ::std::basic_string<CharT, Traits, Allocator>
  >
  struct basic_string:
    Base
  {
    using Base::Base;
    basic_string( CharT const* ptr ):
      Base( ptr?Base(ptr):Base() )
    {}
  };
  using string = basic_string<char>;
  using wstring = basic_string<wchar_t>;
}

Этот safer::string в основном идентичен std::string, но не сбой при построении из нулевого указателя. Вместо этого он рассматривает его как пустую строку.

Просто запишите все упоминания std::string из вашей базы кода и замените на safer::string и аналогичные для std::wstring и std::basic_string.

void crash(const safer::string& s) {}

Вы могли бы вместо бросить, а не тихо использовать значение.

Мы также можем обнаружить 0 во время компиляции:

namespace safer {
  template<class CharT,
    class Traits = ::std::char_traits<CharT>,
    class Allocator = ::std::allocator<CharT>,
    class Base = ::std::basic_string<CharT, Traits, Allocator>
  >
  struct basic_string:
    Base
  {
    using Base::Base;
    basic_string( CharT const* ptr ):
      Base( ptr?Base(ptr):Base() )
    {}
    template<class T,
      // SFINAE only accepts prvalues of type int:
      std::enable_if_t<std::is_same<T, int>::value, bool> = true
    >
    basic_string(T&&)=delete; // block 0
  };
  using string = basic_string<char>;
  using wstring = basic_string<wchar_t>;
}

и теперь передача 0 получает ошибку времени компиляции, передача nullptr или null char const* получает пустую строку.

живой пример.