Как вернуть ссылку на константу, которая недоступна
Скажем, у меня есть следующая функция:
const std::string& Cat::getKittenName() const
{
Kitten* kitty = getKitty();
return kitty->getName();
}
Где Kitten::getName
возвращает const std::string&
, как лучше всего обрабатывать случай, когда kitty
является nullptr
? Я мог бы вернуться std::string("")
, но затем возвращаю ссылку на временное и практически гарантирующее поведение undefined. Я мог бы изменить функцию getKittenName
, чтобы вернуть std::string
, чтобы обойти это, но затем я представляю избыточную копию для всех случаев, когда kitty
доступен. Сейчас я чувствую, что лучший вариант:
const std::string& Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
{
return kitty->getName();
}
static std::string empty("");
return empty;
}
Единственная проблема может быть, если "волшебная статика" недоступна. Есть ли проблемы с этим решением или есть лучший способ сделать это?
Ответы
Ответ 1
На самом деле у вас есть несколько вариантов.
-
Самый простой способ - вернуть std::string
, но вы упомянули, что не хотите этого по соображениям производительности. Я бы сказал, что вы должны сначала создать профиль, чтобы убедиться, что он будет представлять заметную проблему с производительностью, потому что все другие решения сделают код более сложным и, следовательно, по меньшей мере немного сложнее в обслуживании. Но пусть говорят, что это кажется значительным.
-
Если вы беспокоитесь о том, что статическая функция функции-невидимки не реализована, вы можете создать резервное значение как статический член Cat
:
class Cat {
static const std::string missingKittenName;
public:
const std::string& Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
return kitty->getName();
else
return missingKittenName;
}
};
-
Так как Kitten::getName()
, очевидно, возвращает ссылку (в противном случае вы не будете беспокоиться о копиях), вы также можете вернуть указатель:
const std::string* Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
return &kitty->getName();
else
return nullptr;
}
-
Вы можете вернуть необязательную ссылку на строку:
boost::optional<const std::string&> Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
return kitty->getName();
else
return boost::none;
}
-
Если тот факт, что имя отсутствует, является исключением (ошибка), вы можете создать исключение:
const std::string& Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
return kitty->getName();
else
throw std::invalid_argument("Missing kitten");
}
Ответ 2
Возвращает ссылку на константу static std::string.
Причины:
- "волшебная статика" не волшебна, они являются частью стандарта С++.
- статики строятся при первом прохождении кода
(т.е. когда-либо)
- as С++ 11 статическая конструкция является потокобезопасной.
- статические объекты правильно освобождаются в правильном порядке при
конец программы
- ограничение производительности одного избыточного статического объекта совершенно незначительно и намного меньше, чем стоимость тестирования возвращаемых указателей на нуль.
Если вы используете многопоточность в компиляторе pre-С++ 11, вам нужно будет написать потокобезопасный синглтон для создания строки по умолчанию или определить ее в области файлов.
С++ 11:
const std::string& Cat::getKittenName() const
{
static const std::string noname { /* empty string */ };
Kitten* kitty = getKitty();
if (kitty)
{
return kitty->getName();
}
return noname;
}
С++ 03:
namespace {
const std::string noname;
}
const std::string& Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
{
return kitty->getName();
}
return noname;
}
Ответ 3
Возвращает логическое значение, указывающее успешность/сбой, и передает результат по указателю.
Вот пример:
bool Cat::getKittenName(std::string *name) const {
Kitten* kitty = getKitty();
if (kitty) {
*name = kitty->getName();
return true;
}
return false;
}
Вы использовали бы это следующим образом:
std::string name;
if(mycat.getKittenName(&name)) {
// name is populated and is valid
}
Это требует, чтобы класс, передаваемый указателем, имел действительный конструктор копирования и оператор присваивания копии.
Есть несколько причин, которые я предпочитаю делать так:
- Возврат указателя является сбивающим с толку, и он не дает понять вызывающему, что делать с указателем.
Например, приведенный выше пример:
const std::string* Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
return &kitty->getName();
else
return nullptr;
}
будет вызываться следующим образом:
const std::string *name = mykitty.getKittenName();
o.k... теперь что? Я не могу изменить значение имени, используя этот указатель, но могу ли я изменить то, на что указывает указатель? Должен ли я обернуть это в
std::unique_ptr<const std::string>
?? В общем, возвращающие указатели очень C-esque и являются отличным источником утечек памяти и ошибок в дополнение к запутыванию.
- Выбрасывание исключения
В этом примере:
const std::string& Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
return kitty->getName();
else
throw std::invalid_argument("Missing kitten");
}
Я не поклонник, потому что он поощряет бросать исключения для не исключительного поведения. Например, если вы написали метод "find()" на наборе котят. Это не "исключение", чтобы не найти котенка по определенному имени. STL использует различные механизмы, такие как std:: end и std:: pair, чтобы избежать исключения исключений для не исключительного поведения.
Другая проблема, которую это порождает, заключается в том, что, в отличие от Java, не совсем ясно, где исключаются исключения в коде, а транзитивные вызовы не могут корректно очищаться, если вы не понимаете, что что-то бросает. Это особенно опасно в библиотеках, которые генерируют исключения.
Я предпочитаю стиль:
retval method(const input&, pointer *output) { }
потому что это ясно, как входы и выходы размещаются на методе. Это также позволяет избежать возврата фанковых конструкций, таких как std:: pair, которые не масштабируются.