tags:

views:

1620

answers:

7

I'm used to passing around string like this in my C++ applications:

void foo(const std::string& input)
{
  std::cout << input.size() << std::endl;
}

void bar()
{
  foo("stackoverflow");
}

Now I have a case where I want the string to be NULL:

void baz()
{
  foo("stackoverflow");
  foo(NULL); // very bad with foo implementation above
}

I could change foo to:

void foo(const std::string* input)
{
  // TODO: support NULL input
  std::cout << input->size() << std::endl;
}

But to pass a string literal or copy a char* to that implementation of foo I need to write something like this:

void bar()
{
  string input("hi"); // annoying temporary
  foo(&input);
  foo(NULL);  // will work as long as foo handles NULL properly
}

I started thinking about inheriting from std::string and adding a null property, but I'm not so sure it's a good idea. Maybe it is better to simply use a const char* string for parameters that can be NULL, but what if I want to save a copy of the string (or NULL) without having to manage its memory myself? (See What are some of the drawbacks to using C-style strings? etc.)

Any clever solution around?

+1  A: 

What if you just use:

void foo(const char *xinput)
{
    if (xinput == NULL) {
        // do something exceptional with this
        return;
    }
    std::string input(xinput);
    // remainder of code as usual
}

Yes, this does incur an extra allocation and copy, and calling the function is a bit more verbose because you need to use .c_str() in the usual case, but it does give you the semantics you want.

Greg Hewgill
+11  A: 

Personally, I would change the semantics to pass around empty std::strings instead of NULL:

void foo(const std::string& input)
{
    if (!input.empty())
        std::cout << input.size() << std::endl;
}

void bar()
{
      foo("");
}
Max Lybbert
Two points: first, an empty string can be valid value, separate from null. Second, it's considered a better idea to use `empty()` instead of `size()` when all you care about is whether the size is non-zero.`boost::optional`, or pointers, are a better solution.
Head Geek
Changed to use empty(). A comment on the original question points to using NULL to signify "don't know the value" and empty to signify "empty value." Which makes this answer wrong. But I would take a second look at things as passing around pointers brings in "who owns this pointer?" problems.
Max Lybbert
Either boost::optional or a smart pointer would be the best choice in that case.
Head Geek
+13  A: 

If you want the type to be null, then make it a pointer. Pass string pointers around instead of references, since this is precisely what pointers can do, and references cant. References always point to the same valid object. Pointers can be set to null, or be reseated to point to another object. Thus, if you need the things pointers can do, use pointers.

Alternatively, use boost::optional, which allows a more type-safe way to specify "this variable may or may not contain a value".

Or, of course, change the semantics so you either use empty strings instead of null, pass a separate bool parameter specifying whether the string is available or not, or refactor so you don't need this in the first place.

jalf
+1 for letting me know of boost::optional
divideandconquer.se
+2  A: 

Why don't you overload the function and give the second overload no argument? Then both overloads can internally call a function that provides the read logic and that, itself, gets passed a pointer to std::string.

void foo_impl(string const* pstr) { … }

void foo(string const& str) {
    foo_impl(&str);
}

void foo() {
    foo_impl(0);
}
Konrad Rudolph
+11  A: 

Function overloading to the rescue...

void foo( const std::string& input )
{
    std::cout << input << std::endl;

    // do more things ...
}

void foo( const char* input )
{
    if ( input != NULL ) foo( std::string(input) );
}

This will accept both c-style char arrays and std::strings, and will incur extra overhead on the stack if you pass in a string literal or a char array, but allows you to keep your implementation in one place and keep your nice syntax.

eplawless
I wouldn't do this, it might be misleading.
sudarkoff
That's the point, really; it's an abstraction. Usage should be straightforward, unless you're extremely concerned about performance.
eplawless
@sudarkoff : I disagree. The problem itself is misleading, and needs an abstraction. The solution is elegant. As about the performance problem, "Premature Optimization, etc."... +1.
paercebal
+3  A: 

Or, mixing a bit of two previous answers:

void fooImpl( const char* input )
{
    if ( input != NULL )
        std::cout << input << std::endl;
}

void foo( const std::string& input )
{
    fooImpl(input.c_str());    
}

void foo( const char* input )
{
    fooImpl(input);
}

Same interface, no copy on the stack. You could, if you liked, inline fooImpl as well.

Matt McClellan
+2  A: 

Absolutely do not inherit from std::string. Inheritance is the tightest coupling you can have in C++, and you're only looking for nullability, which you can get simply with const char*, overloads, or simply std::string * if you really want.

Tom