tags:

views:

395

answers:

5

I'm trying to work through ways to create a class with a std::string argument, but which also handles NULL without throwing an exception. Here's an example of the code:

class myString {
public:
    myString(const std::string& str) : _str(str) {}
    std::string _str;
};

int main() {
    myString mystr(NULL);
    printf("mystr = <%s>\n", mystr._str.c_str());
    return 0;
}

Intuitively, you'd think that this program should print "mystr = <>" and exit successfully, but instead with g++ it gives this error:

terminate called after throwing an instance of 'std::logic_error'  
what():  basic_string::_S_construct NULL not valid

How can I change this class so that it translates NULL into "" instead of throwing a logic_error exception?

(Postscript: As you'd suspect, the "real" implementation is a bit more complicated than this example, having several arguments in the constructor and doing more interesting things with _str -- and properly keeping it private. The motivation behind this example is that programmers in our team will tend to use NULL in this context for historical purposes, and the compiler won't catch this. Retraining everyone to always use "" is relatively hard. Alternatively, a clever solution would be easy)

+5  A: 

You cannot. You need to have something to be able to pass a reference to. You could though work around this by defaulting to null-strings aka "".

Alternatively, have another explicit ctor:

class myString {
 public:
   myString(const std::string& str) : _str(str) {}
   explicit myString(const char* str = NULL) : _str(str?str:"") {}
   std::string _str;
};
dirkgently
Is the 'explicit' keyword necessary in this context? I tried running this example without explicit, passing it NULL, string("test"), and "test", and it found the correct constructor in all three cases.
Matt Ball
No, purely defensive programming.
dirkgently
+6  A: 

Really, you can't. NULL is not a string. But here are some alternatives:

  • You could overload your constructor to omit the string argument.

  • You could use a pointer instead of a reference.

  • You could default the string argument to "".

Fred Larson
+2  A: 

You need to provide a constructor that accepts a pointer as an argument. NULL is a pointer, not a reference, and so as you've seen disaster will result if you try to treat it as a string reference.

Try:

class myString {
public:
    myString(const std::string& str) : _str(str) {}
    myString(const std::string* str) : { /* special-case logic here */}
    std::string _str;
};
David Seiler
The problem is not that he is treating NULL as a reference. But that the constructor of std::string accepts a NULL and this will try and build a temporary object before calling the mystring constructor.
Martin York
+11  A: 

What actually happens here is that NULL is interpreted as char const* and is being tried to be converted to std::string (you can convert C-strings of type char* into STL strings, right?).

To handle it, you may add another constructor

class myString {
public:
    myString(const std::string& str) : _str(str) {}
    myString(const char* str) : _str(str==NULL?"":std::string(str)) 
    { /* and do something special in the body */ }
    std::string _str;
};

But if you need nothing special there, the better way would be

class myString {
public:
    myString(const std::string& str = "") : _str(str) {}
    std::string _str;
};
int main() {
    myString mystr;  //no () here!
    printf("mystr = <%s>\n", mystr._str.c_str());
    return 0;
}
Pavel Shved
Does that code cause the infamous "most vexing parse" or a related scenario? Usually explicitly calling the null-arg constructor as you do in the first line of main() will do that. I didn't try compiling it myself.
rmeador
After the edit it won't. :-)
Pavel Shved
This is the trick I was looking for! I've added the second constructor to handle const char *. The second half of the example doesn't apply in my case because there are other parameters in the constructor (but I originally didn't tell you that).
Matt Ball
You probably meant `(str==NULL?"":str)` as `str` and `""` both are `const char*`s
MSalters
@MSalters , well, whatever. They're assigned to `_str` variable which is of type `std::string` anyway.
Pavel Shved
A: 

I'll assume this problem isn't as simple as your example appears to be.

Instead of using a NULL parameter, you can create a static variable and assign to that.

class myString {
public:
    static std::string nullstring;
...
};

and somewhere in a .cpp:

std::string myString::nullstring("");

And to call it:

int main() {
    myString mystr(myString::nullstring);
    printf("mystr = <%s>\n", mystr._str.c_str());
    return 0;
}
Mark Ransom
That does not stop sombody accidently passing NULL.
Martin York
As you've probably guessed, the real implementation is not as simple as the example, and this particular approach isn't quite suited to the problem (see newly added postscript section in the question)
Matt Ball
ebasconp
ok, maybe your implemnetation is not as simple as your example, but what you are trying to do goes against the "referencing" concept: passing a NULL to a reference does not make any sense. Consider modifying your implementation to receive pointers instead of references or receive default parameters as this answer suggests.
ebasconp
To be honest, I'm not sure why the code in the question is able to compile without error.
Mark Ransom
Matt Ball
Matt Ball
Mark Ransom
@Mark: it works because the NULL is passed to the std::string(const char*) constructor before passing the reference to this new object to the myString constructor.
ebasconp
Mark Ransom