Нарушение доступа при отправке 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, или же я?
Какая наилучшая практика для написания прототипов, которые избегают этой проблемы?
Ответы
Ответ 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*
получает пустую строку.
живой пример.