如果不能为空堆栈 return nullptr 如何保护用户?

How to protect user if can't return nullptr for an empty stack?

我正在尝试编写此 Coursera course example of Towers of Hanoi 中给出的 Cube、Stack 示例的实现代码,以学习更多 C++。

stack.h中我必须实现:

class Stack {
public:
void push_back(const Cube & cube);
Cube removeTop();
Cube & peekTop();
unsigned size() const;

friend std::ostream & operator<<(std::ostream & os, const Stack & stack);

private:
std::vector<Cube> cubes_;
};

我遇到的问题是 removeTop()。如果堆栈(向量)为空,我正在考虑 returning nullptr,因为 pop_back 的行为对于空向量是未定义的。

Calling pop_back on an empty container is undefined. Cpp Reference

inline Cube Stack::removeTop() {
  if (!cubes_.empty()) {
    Cube top_cube = cubes_.back();
    cubes_.pop_back();
    return top_cube;
  }
  else {
    return nullptr;
  }
}

但是,我在编译过程中遇到错误。

./stack.h:35:12: error: no viable conversion from returned value of type
      'std::__1::nullptr_t' to function return type 'uiuc::Cube'
    return nullptr;

如果我不能 return nullptr,我该如何保护用户?我是否仅限于告诉用户不应在空堆栈上调用该函数并让 him/her 负责检查?

可能是这样的:

inline bool Stack::removeTop(Cube& top_cube) {
  if (!cubes_.empty()) {
    top_cube = cubes_.back();
    cubes_.pop_back();
    return true;
  }
  else {
    return false;
  }
}

基于函数签名,你必须实现,你真的不能。通常,您会用断言来保护这类事情。在某些情况下,您可能会使用 NullObject pattern,或者您可以 return 一个垃圾对象。在较新的 C++ 版本中,您还可以使用 std::optional<T>.

inline Cube Stack::removeTop() {
  if (!cubes_.empty()) {
    Cube top_cube = cubes_.back();
    cubes_.pop_back();
    return top_cube;
  }
  else {
    return Cube {};
  }
}

正如 pyj 指出的那样,自 c++17 以来,有一种新机制可以这样做(采用 boost::optional 想法)。这是 std::optional 容器。当使用std::optional时,你告诉这个函数的用户,他可能会得到一个空响应,因此,他必须检查是否初始化。

来自 cpp 参考:

The class template std::optional manages an optional contained value, i.e. a value that may or may not be present.

A common use case for optional is the return value of a function that may fail. As opposed to other approaches, such as std::pair, optional handles expensive-to-construct objects well and is more readable, as the intent is expressed explicitly.

现在开始使用:

inline std::optional<Cube> Stack::removeTop() 
{
    if (!cubes_.empty()) 
    {
        Cube top_cube = cubes_.back();
        cubes_.pop_back();
        return top_cube;
    }
    else 
    {
        return std::nullopt;
    }
}

而且,当你得到一个 std::optional 而不是 Cube 时,堆上没有 内存分配 ,这比使用指针有很大的优势相同的输出。

If an optional contains a value, the value is guaranteed to be allocated as part of the optional object footprint, i.e. no dynamic memory allocation ever takes place. Thus, an optional object models an object, not a pointer, even though operator*() and operator->() are defined.

这正是异常的用途:

if (cubes_.empty())
    throw std::runtime_error("stack underflow");
Cube top_cube = cubes_.back();
cubes_.pop_back();
return top_cube;

std::optional 将其复杂化几乎可以肯定 不是 这里的正确答案。试图从空栈中弹出意味着程序迷路了。这应该是一个硬错误,而不是被显示 "you might or might not have this, please check afterwards".

的界面所掩盖的错误