Access Violation when sending a 0 int literal to a const string parameter

前端 未结 2 2041
清歌不尽
清歌不尽 2021-01-08 00:05

On VS2015 and VS2017, this compiles with no warning, and generates an access violation that cannot be caught and crashes the application. Obviously the int 0 is silently con

相关标签:
2条回答
  • 2021-01-08 00:12
    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>;
    }
    

    This safer::string is basically identical to std::string but doesn't crash when constructed from a null pointer. Instead, it treats it as an empty string.

    Simply sweep all mention of std::string from your code base and replace with safer::string, and similar for std::wstring and std::basic_string.

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

    You could choose to throw rather than silently consume the value.

    We can detect 0 at compile time as well:

    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>;
    }
    

    and now passing 0 gets a compile-time error, passing nullptr or a null char const* gets you an empty string.

    live example.


    So, there are people who get nervous about the fact I told you to inherit from a non-polymorphic type in std. There are reasons not to inherit from a non-polymorphic type, but none of them apply here.

    In general, however, be careful when inheriting from types not designed for polymorphism like std::basic_string<CharT>. In this particular case, storing a safer::basic_string<T> in a std::basic_string<T>* and then calling delete on it is undefined behavior (or in a std::unique_ptr<std::basic_string<T>> which calls delete). But dynamically allocating basic_strings is usually a mistake in the first place, so that is unlikely to occur.

    In addition, this inheritance has to obey the LSP without changing the behavior of any method of the base class. In this case we are adapting construction, and construction is never polymorphic. If we had any write operations that were not construction where we wanted to maintain an invariant in the descended class we would be in trouble.

    0 讨论(0)
  • 2021-01-08 00:34

    For this specific case, you can get a compile time error by using C++11 std::nullptr_t, just add the following deleted overload:

    void crash(std::nullptr_t) = delete;
    

    Of course, this won't protect you against passing null (or non-null-terminated ) char* pointers ... you are violating an std::string constructor precondition, resulting in undefined behavior; this is by definition unrecoverable.

    Alternatively, if you really need to catch these errors at runtime in a possibly recoverable way, you could write a const char* overload that throws if given a null pointer or invokes the std::string const& version otherwise.

    If your real function takes more than a few string arguments, and overloading all possible combinations seems not feasible, you could resort writing a function template, performing all the checks over the deduced types ex-post.

    0 讨论(0)
提交回复
热议问题