可以为NULL的C++字符串

7

我习惯在我的C++应用程序中像这样传递字符串:

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

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

现在我有一个情况,想要将字符串设置为NULL:

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

我可以将foo更改为:

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

但是要将字符串字面量传递或将char*复制到foo的实现中,我需要编写类似于以下内容的代码:

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

我开始考虑从std::string继承并添加一个null属性,但我不确定这是否是一个好主意。也许更好的方法是对于可以为NULL的参数,仅使用const char*字符串,但如果我想保存一个字符串(或NULL)的副本而不必自己管理其内存怎么办?(参见使用C风格字符串有哪些缺点?等)

有没有什么聪明的解决方案?


为什么你想要一个NULL? ""已经足够了吗? - Tim
@Tim:也许吧。这样可以避免我总是检查那些空参数,而且我会使用empty()来检查“没有值”。 - activout.se
重新引入NULL是虚假的。如果你需要可怕的NULL指针,为什么一开始就要使用引用?实际上,使用引用最重要的原因之一是可以确保它不是NULL。 - Thorsten79
7个回答

20

如果您希望类型为空,则需要将其作为指针。请传递字符串指针而不是引用,因为这正是指针所能做的,而引用无法做到。引用始终指向同一个有效对象,而指针可以设置为null,或重置为指向另一个对象。因此,如果您需要指针可以实现的功能,请使用指针。

或者,您可以使用boost::optional,它允许更加类型安全地指定“此变量可能包含值,也可能不包含值”。

当然,您还可以更改语义,使用空字符串代替null,传递单独的bool参数指定字符串是否可用,或者重新设计以避免出现此问题。


11

函数重载来解救...

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

这将接受c风格的字符数组和std::strings,并且如果你传入一个字符串字面值或者字符数组,它会在堆栈上产生额外的开销,但是可以让你将实现保留在一个地方并保持漂亮的语法。


1
我不会这样做,因为这可能会引起误解。 - sudarkoff
这就是关键,它是一种抽象。使用起来应该很简单,除非你非常关注性能。 - eplawless
1
@sudarkoff:我不同意。问题本身是误导性的,需要抽象化处理。解决方案是优雅的。至于性能问题,“过早优化等等”... +1。 - paercebal

10

就我个人而言,我会更改语义使其传递空的std::字符串而不是NULL:

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

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

1
两点:首先,空字符串可以是有效值,与null分开。其次,当您只关心大小是否为非零时,使用empty()而不是size()被认为是更好的选择。boost::optional或指针是更好的解决方案。 - Head Geek
改用empty()。原问题的评论指出使用NULL表示“不知道值”,而使用empty表示“空值”。这使得这个答案是错误的。但是,当传递指针时,需要仔细考虑“谁拥有这个指针?”的问题。 - Max Lybbert
在这种情况下,boost::optional或智能指针都是最好的选择。 - Head Geek

3
或者,结合前面两个答案的一些内容:
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);
}

相同的接口,没有在堆栈上复制。如果您愿意,也可以将fooImpl内联。


2

绝对不要从std::string继承。在C++中,继承是最紧密的耦合方式,而你只需要检查是否为空,这可以通过const char*、重载或者如果你真的需要的话,简单地使用std::string *来实现。


2
为什么不重载函数并给第二个重载没有参数呢?然后两个重载可以内部调用一个提供读取逻辑的函数,该函数本身会传递指向std::string的指针。
void foo_impl(string const* pstr) { … }

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

void foo() {
    foo_impl(0);
}

1

如果你只使用:

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

是的,这确实会产生额外的分配和复制,并且调用函数会更加冗长,因为您需要在通常情况下使用.c_str(),但它确实可以给您想要的语义。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接