C ++ строка, которая может быть NULL - PullRequest
7 голосов
/ 04 декабря 2008

Я привык передавать строку в моих приложениях на 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, но я не уверен, что это хорошая идея. Может быть, лучше просто использовать строку const char* для параметров, которые могут иметь значение NULL, но что если я захочу сохранить копию строки (или NULL) без необходимости самостоятельно управлять ее памятью? (См. Каковы некоторые недостатки использования строк в стиле C? и т. Д.)

Любое умное решение вокруг?

Ответы [ 7 ]

20 голосов
/ 04 декабря 2008

Если вы хотите, чтобы тип был нулевым, сделайте его указателем. Передавайте строковые указатели вместо ссылок, поскольку именно это могут делать указатели, а ссылки не могут. Ссылки всегда указывают на один и тот же действительный объект. Указатели могут быть установлены на нуль или переустановлены, чтобы указывать на другой объект. Таким образом, если вам нужно то, что могут делать указатели, используйте указатели.

В качестве альтернативы используйте Boost :: Optional, что позволяет более безопасным для типов способом указать «эта переменная может содержать или не содержать значение».

Или, конечно, измените семантику так, чтобы вы использовали пустые строки вместо нуля, передавали отдельный параметр bool, указывающий, доступна строка или нет, или выполняли рефакторинг, чтобы вам это не понадобилось с самого начала. 1005 *

11 голосов
/ 04 декабря 2008

Функция перегрузки на помощь ...

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 и приведет к дополнительным накладным расходам в стеке, если вы передадите строковый литерал или массив char, но позволит вам сохранить реализацию в одном месте и сохранить ваш хороший синтаксис.

11 голосов
/ 04 декабря 2008

Лично я бы изменил семантику так, чтобы она передавала пустые std :: strings вместо NULL:

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

void bar()
{
      foo("");
}
3 голосов
/ 04 декабря 2008

Или, смешивая немного двух предыдущих ответов:

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 голосов
/ 05 декабря 2008

Абсолютно не наследуется от std::string. Наследование - это самая тесная связь, которую вы можете иметь в C ++, и вы ищете только обнуляемость, которую вы можете получить просто с const char*, перегрузками или просто std::string *, если вы действительно хотите.

2 голосов
/ 04 декабря 2008

Почему бы вам не перегрузить функцию и не дать второй перегрузке аргумент? Тогда обе перегрузки могут внутренне вызывать функцию, которая обеспечивает логику чтения и которая сама получает указатель на std::string.

void foo_impl(string const* pstr) { … }

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

void foo() {
    foo_impl(0);
}
1 голос
/ 04 декабря 2008

Что, если вы просто используете:

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() в обычном случае, но он дает вам семантику, которую вы хотите.

...