赞
踩
在经典数据结构,stack
和queue
中有一个重要的函数那就是pop()
表示弹出线性顶部的一个元素。
而在各种语言的标准数据结构中也自然有这些数据结构和对应的函数。
在C++中,pop()无返回,且对空对象pop()行为未定义。
空对象未定义可以理解,但是为什么不返回顶部元素呢?这涉及到异常安全的问题:Exception-Safe Coding in C++ (exceptionsafecode.com)
在C++早期由于没有移动语义和其他各种原因,无法做到安全的返回。
本文就主要来处理这两个问题。
python:
使用list()
当作栈。虽然在空栈时pop也会出异常,但是py的pop可以返回被pop出的数据。
stk = []
n = 3
for i in range(n):
stk.append(i)
for i in range(n):
# 获取pop的数据
print(stk.pop())
# IndexError: pop from empty list
# stk.pop()
print(len(stk))
pop()
函数在空栈时是未定义行为。
楼主本地测试下,msvc直接崩,gcc可以往下运行,但是若访问空栈的top()也会崩。这只是玩个乐呵,别做任何参考。
但C++的一个特点是pop()函数返回是void
!
#include <iostream> #include <stack> std::stack<int> stk; auto test_pop() { // printf("Before pop size=%d top=%d\n", stk.size(), stk.top()); stk.pop(); // printf("After pop size=%d top=%d\n", stk.size(), stk.top()); } int main() { std::cout << "Main Start\n"; constexpr int n = 3; for (int i = 1; i <= n; i += 1) { stk.push(i); } for (int i = 0; i < n; i += 1) { test_pop(); } // 空栈pop是未定义 test_pop(); std::cout << "Main End\n"; }
下面为了方便,采用继承而不是组合的方式来处理。请注意在调用模板基类内容时候的一些注意点,本文不会讲解这块基础。
有一些激进派认为,空栈的pop直接抛出一个确定的异常,但本文没那么粗暴。
且默认采用移动语义,缺点是对于一些确定删除移动语义的对象会报错,当然这类对象比较少。
这是一种非常朴素和经典的方案。在C语言时代,没有引用语义,因此采用指针的方式处理。
因此可以写出两种接口:
bool pop(Type &val)
Type pop(bool &ok)
其中return bool比较常见。而第二种在Qt库中出现的也比较多。
方案1
如果是空栈一般就不要对引用的对象做多余操作了。
这样的话,对于外部传入的对象的创建是多余的性能开销。
方案2
当失败时,由于返回值的存在,需要有一个默认的返回。但是不是所有对象的构造方式都一致。
因此这也是这种方式的一个缺点。
#include <iostream> #include <stack> namespace my { template <typename Type> class stack : public std::stack<Type> { public: bool pop(Type &val) { if (this->empty()) { // 一般来说直接false // 不对&进行多余操作 return false; } val = std::move(this->top()); std::stack<Type>::pop(); return true; } Type pop(bool &ok) { if (this->empty()) { ok = false; return {}; } Type ret = std::move(this->top()); std::stack<Type>::pop(); ok = true; return ret; } }; } // namespace my my::stack<int> stk; void test_pop_1() { bool ok; int x = stk.pop(ok); printf("pop=[%d]{%s}\n", x, ok ? "true" : "false"); } void test_pop_2() { int x = -1; if (stk.pop(x)) { printf("pop=[%d]{true}\n", x); } else { // false时候的val一般视为随机值,垃圾值 printf("pop=[%d]{false}\n", x); } } int main() { std::cout << "Main Start\n"; constexpr int n = 3; for (int i = 1; i <= n; i += 1) { stk.push(i); } // auto test_pop = test_pop_1; auto test_pop = test_pop_2; for (int i = 0; i < n; i += 1) { test_pop(); } // 空栈pop是未定义 test_pop(); std::cout << "Main End\n"; }
这里采用shart_ptr<>
却使用make_share<>()
。
智能指针在栈内的开销是常量级的。且可以直接作为空指针来进行bool判断。
#include <iostream> #include <memory> #include <stack> namespace my { template <typename Type> class stack : public std::stack<Type> { public: std::shared_ptr<Type> pop() { if (this->empty()) { return nullptr; } std::shared_ptr<Type> ret = std::make_shared<Type>(std::move(this->top())); std::stack<Type>::pop(); return ret; } }; } // namespace my my::stack<int> stk; void test_pop() { auto sp = stk.pop(); if (sp) { printf("pop=[%d]{true}\n", *sp); } else { printf("pop=[nullptr]{false}\n"); } } int main() { std::cout << "Main Start\n"; constexpr int n = 3; for (int i = 1; i <= n; i += 1) { stk.push(i); } for (int i = 0; i < n; i += 1) { test_pop(); } // 空栈pop是未定义 test_pop(); std::cout << "Main End\n"; }
在C++17中为了处理对象是否有效的这样方案,直接引入了一个新的对象std::optional
。
std::optional
可以直接作为bool的判断。且同样保证了已知的构造方式。
#include <iostream> #include <optional> #include <stack> namespace my { template <typename Type> class stack : public std::stack<Type> { public: std::optional<Type> pop() { if (this->empty()) { return {}; } auto ret = std::move(this->top()); std::stack<Type>::pop(); return std::move(ret); } }; } // namespace my my::stack<int> stk; void test_pop() { auto opt = stk.pop(); if (opt) { printf("pop=[%d]{true}\n", opt); } else { printf("pop=[%d]{false}\n", opt); } } int main() { std::cout << "Main Start\n"; constexpr int n = 3; for (int i = 1; i <= n; i += 1) { stk.push(i); } for (int i = 0; i < n; i += 1) { test_pop(); } // 空栈pop是未定义 test_pop(); std::cout << "Main End\n"; }
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。