如何返回不可用的常量引用

How to return a const reference which is not available

本文关键字:常量 引用 何返回 返回      更新时间:2023-10-16

假设我有以下函数:

const std::string& Cat::getKittenName() const
{
Kitten* kitty = getKitty();
return kitty->getName();
}

Kitten::getName返回const std::string&我如何最好地处理kittynullptr的情况?我可以返回std::string("")但随后我返回对临时且实际上保证未定义行为的引用。我可以更改getKittenName函数以返回一个std::string来解决此问题,但随后我为所有可用的kitty情况引入了冗余副本。现在我觉得最好的选择是:

const std::string& Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
{
return kitty->getName();
}
static std::string empty("");
return empty;
}

唯一的问题可能是"魔术静态"不可用。此解决方案是否有任何问题,或者有更好的方法吗?

你有几个选择,真的。

  • 最简单的方法是返回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;
    }
    

    从 C++17 开始,optional作为std::optional成为标准库的一部分,因此不再需要回退到 Boost。

  • 如果缺少名称的事实属于异常情况(错误),则可以引发异常:

    const std::string& Cat::getKittenName() const
    {
    Kitten* kitty = getKitty();
    if (kitty)
    return kitty->getName();
    else
    throw std::invalid_argument("Missing kitten");
    }
    

返回对 const 静态 std::string 的引用。

原因:

  • "魔术静力学"不是魔术,它们是C++标准的一部分。
  • 静态是在代码第一次流过它们时构造的 (即曾经一次)
  • 从 C++11 开始,静态构造是线程安全的。
  • 静态对象以正确的顺序正确释放,位于 节目结束
  • 一个冗余静态对象的性能损失完全可以忽略不计,并且远低于测试返回的 NULL 指针的成本。

如果在 c++11 之前的编译器上是多线程的,则需要编写线程安全的单一实例来制造默认字符串,或在文件范围内定义它。

C++11:

const std::string& Cat::getKittenName() const
{
static const std::string noname { /* empty string */ };
Kitten* kitty = getKitty();
if (kitty)
{
return kitty->getName();
}
return noname;
}

C++03:

namespace {
const std::string noname;
}
const std::string& Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
{
return kitty->getName();
}
return noname;
}

返回一个指示成功/失败的布尔值,并通过指针传递输出。

下面是一个示例:

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
}

这要求指针传入的类具有有效的复制构造函数和复制赋值运算符。

我更喜欢这样做有几个原因:

  1. 返回指针令人困惑,调用方不清楚如何处理指针。

例如上面列出的示例:

const std::string* Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
return &kitty->getName();
else
return nullptr;
}

会这样称呼:

const std::string *name = mykitty.getKittenName();

好吧,现在怎么办? 我不能使用此指针更改 name 的值,但我可以更改指针指向的内容吗?我应该把它包装在一个

std::unique_ptr<const std::string>

??一般来说,返回指针是非常C式的,除了令人困惑之外,还是内存泄漏和错误的重要来源。

  1. 引发异常

此示例:

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::p air )来避免为非异常行为引发异常。

由此产生的另一个问题是,与Java不同,它并不完全清楚代码中抛出异常的位置,并且如果您没有意识到某些内容会抛出某些内容,则传递调用将无法正确清理。 这在引发异常的库中尤其邪恶。

我更喜欢以下风格:

retval method(const input&, pointer *output) { }

因为输入和输出是如何放置在方法上的很清楚的。 这也避免了必须返回时髦的结构,如 std::p air,它无法扩展。

可能最正确的方法是这样的:

#include <optional>
#include <functional>
std::optional<std::reference_wrapper<const std::string>> Cat::getKittenName() const
{
Kitten* kitty = getKitty();
if (kitty)
{
return kitty->getName();
}
return std::nullopt;
}

下面是一个完整的演示:

#include <optional>
#include <functional>
std::optional<std::reference_wrapper<const int>> foo() {
static int a = 5;
if (a > 5) {
return a;
}
return std::nullopt;
}
int bar() {
auto x = foo();
if (x.has_value()) {
return *x;
}
return 0;
}