1

i could not find threads giving a clear answer on this -

i have a constructor, for example:

FanBookPost::FanBookPost(Fan* owner, std::string content);

Fan is another class in my code, but content is the problematic one:

since std::string is not a pointer, i would expect that calling the constructor with (fan, nullptr) would not be possible. however, it compiles! ... and crashes at runtime with EXC_BAD_ACCESS.

is this avoidable?

Zephyer
  • 333
  • 6
  • 16
  • Pass the string by reference. `FanBookPost::FanBookPost(Fan* owner, std::string &content);` – smac89 Jan 21 '14 at 21:16
  • "" is allowed as legit content @Smac89 – Zephyer Jan 21 '14 at 21:17
  • 4
    If you *really* want to, you can overload it with a `const char *` version that forwards if not null. – chris Jan 21 '14 at 21:19
  • 1
    Crash will occure when the `std::string` constructor is called, its not a problem of your code. – Sebastian Hoffmann Jan 21 '14 at 21:19
  • 3
    You can overload it with a function that takes a `std::nullptr_t`. Non-null-pointer-constants won't choose this overload. – dyp Jan 21 '14 at 21:21
  • @chris im gonna do the overload just to be safe. can you post it as comment so i can accept answer? thanks everyone else too for replying – Zephyer Jan 21 '14 at 21:21
  • http://stackoverflow.com/questions/10771864/assign-a-nullptr-to-a-stdstring-is-safe – marcinj Jan 21 '14 at 21:21
  • @ShafikYaghmour The null pointer is a perfectly valid pointer, `char` or otherwise. The only problem is that `std::string`'s constructor does not accept it. –  Jan 21 '14 at 21:22
  • 1
    @dyp Though this will *not* prevent things like `char *c = 0; FanBookPost fbp(..., c);` or even `FanBookPost fbp(..., 0);`. –  Jan 21 '14 at 21:23
  • @dyp, Good one. I like that better. – chris Jan 21 '14 at 21:23
  • @delnan True, but that's "more obviously flawed" IMHO (i.e. it's more obvious the `char*` will be interpreted as a string and is required to point to a null-terminated array). – dyp Jan 21 '14 at 21:23
  • 2
    @delnan `0` is a null pointer constant. – dyp Jan 21 '14 at 21:24
  • 1
    @dyp `0` is not of type `nullptr_t` though. – Mark B Jan 21 '14 at 21:24
  • 1
    @MarkB But the conversion to `nullptr_t` is a Standard Conversion, whereas the conversion to `string` is a User-Defined Conversion. The Standard Conversion is "better", so the `nullptr_t` overload is preferred. – dyp Jan 21 '14 at 21:25
  • @dyp An already existing `char *` being null is much *less* obviously flawed than passing `nullptr` verbatim. RE overload priority: Interesting, thanks for pointing it out! –  Jan 21 '14 at 21:25
  • @delnan It's an error that's harder to detect, I agree, but when you pass a `char*`, I think it's (at least more) reasonable to assume it must be valid. – dyp Jan 21 '14 at 21:26
  • @dyp I don't quite get your angle. Yes, some random `char *` has a higher (namely > 0%) chance of not being null, but when it *is* null, it's much harder to pin down how and why. Also, given that many people (myself included) are very inclined to code for the happy case ignore the possibility of pointers being null if it's not shoved in their face, seeing `f(c_str)` they won't check whether (1) `f` accepts null or (2) `c_str` can't be null. In contrast, `f(nullptr)` makes it hard to ignore the possibility of a null pointer. –  Jan 21 '14 at 21:30
  • 1
    @delnan My remark wasn't about error detection, but about assuming what happens. When I see `f(nullptr)` compile, I'm more inclined to assume that `f` can handle null pointers than when I see this fails but (only) `f(c)` compiles. Of course, a check for null pointers is reasonable and useful in an interface (essentially where you don't want to rely on the user of the interface keeping a non-nullptr guarantee). – dyp Jan 21 '14 at 21:33

4 Answers4

3

The problem here is that the crash will occure when the constructor of std::string is called (as a nullptr interpreted as const char* is accessed there). There is nothing here you can do against this but to tell other peoples not to do shit. Its not a problem of your constructor and thus not your responsibility (besides that you cant prevent it).

Sebastian Hoffmann
  • 11,127
  • 7
  • 49
  • 77
3

What you're observing is that you can implicitly create a std::string from a character pointer (nullptr in this case) which is then passed to the function. However creating a string from a null pointer is not allowed. There is nothing wrong with your method signature, just the client use that violates the std::string constructor's contract.

Mark B
  • 95,107
  • 10
  • 109
  • 188
1

How about using a proxy / wrapper typ, if you really want to be safe:

template<typename T>
struct e_t
{
public:
    inline e_t ( e_t const & other )
        : m_value( other.m_value )
    {}

    inline T & value( void )                { return m_value; }
    inline operator T&()                    { return m_value;   }
    inline e_t( const T& c ) : m_value( c )     {}

private:
    T m_value;
};

void FanBookPost(int* owner, e_t<std::string> content) {
}

int main()
{
    int n = 0;
    //FanBookPost(&n, 0); // compiler error
    //FanBookPost(&n, nullptr); // compiler error
    //FanBookPost(&n, ""); // unfortunately compiler error too
    FanBookPost(&n, std::string(""));
}
marcinj
  • 48,511
  • 9
  • 79
  • 100
1

The problem is that std::string has a non-explicit ctor that takes a char * as its sole (required) parameter. This gives an implicit conversion from nullptr to std::string, but gives undefined behavior, because that ctor specifically requires a non-null pointer.

There are a few ways to prevent this. Probably the most effective would be to take a (non-const) reference to a std::string, which will require passing a (non-temporary) string as the parameter.

FanBookPost::FanBookPost(Fan* owner, std::string &content);

This does have to unfortunate side effect of giving the function the ability to modify the string that's passed. It also means that (with a conforming compiler1) you won't be able to pass nullptr or a string literal to the function--you'll have to pass an actual instance of std::string.

If you want to be able to pass a string literal, you can then add an overload that takes a char const * parameter, and possibly one that takes a nullptr_t parameter as well. The former would check for a non-null pointer before creating a string and calling the function that takes a reference to a string, and the latter would do something like log the error and unconditionally kill the program (or, just possibly, log the error and throw an exception).

That's annoying and inconvenient, but may be superior to the current situation.


  1. Unfortunately, the last time I noticed MS VC++ did not conform in this respect. It allows passing a temporary object by non-const reference. Normally that's fairly harmless (it just lets you modify the temporary, but that normally has no visible side effects). In this case it's much more troublesome though, since you're depending on it specifically to prevent passing a temporary object.
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • I'd recommend using [`/Za`](http://msdn.microsoft.com/en-us/library/0k0w269d.aspx) (disable language extensions). – dyp Jan 21 '14 at 23:29