std::vector 段错误而不是抛出异常

Posted

技术标签:

【中文标题】std::vector 段错误而不是抛出异常【英文标题】:std::vector segfaulting instead of throwing exception 【发布时间】:2016-11-22 03:02:15 【问题描述】:

我正在尝试为 std::vector 创建一个容器类,以自学更多关于模板、重载运算符和管理异常的知识。

目前,我只是定义基本操作。我有一个下面列出的模板类;我已经用T+=[] 运算符重载为push_backvector,并分别直接访问向量的元素。这按预期工作。

+= 操作符做了它应该做的事情,并且尝试对超出范围的元素使用[] 操作符将按预期抛出异常。

这是原型类和当前的实现:

#include <iostream>
#include <vector>
#include <string>

using namespace std;

template <class T>
class Inventory

    public:
        void operator += (const T& b)    backpack.push_back(b); 

        T operator [] (const unsigned& b)
        
            if (backpack.empty() || backpack.size() < b)
                throw string("Out of Range");
            return backpack[b];
        

        void operator -= (const unsigned& b)
        
            if (backpack.empty() || backpack.size() < b)
                throw string("No such element exists.");
            backpack.erase(backpack.begin() + b);
        

    private:
        vector<int> backpack;
;

int main()

    Inventory<int> pack;
    pack += 2;
    pack += 4;
    try
    
        cout << "It was " << pack[0] << endl;
        cout << "It was " << pack[1] << endl;
        pack -= 0;
        cout << "It is now " << pack[0] << endl;
        //pack -= 1; // Segfaults?
    
    catch (string e)
    
        cout << "Error: " << e << endl;
    

问题在于-= 运算符,旨在擦除右侧指示位置的元素。当我停留在向量的边界内时,这会按预期工作;但是,如果我指定要擦除的越界数字,我不会得到异常;我得到了一个 seg-fault 。我试图通过添加额外的打印命令来确定段错误发生的确切点:

void operator -= (const unsigned& b)

    cout << "In Overload!\n";
    if (backpack.empty() || backpack.size() < b)
    
        cout << "Exception!\n";
        throw string("No such element exists.");
    
    backpack.erase(backpack.begin() + b);

“例外!”线永远达不到。即使我应该评估未定义的行为,程序也会在它达到那个点之前出现故障。我相信我错过了理解这个过程如何工作的关键组成部分。有没有办法我应该写这个所以它可以抛出而不是错误?

在 Linux x64 架构上使用 g++ -std=c++17 -Wall -Wextra -pedantic 编译。

【问题讨论】:

为什么不简单地使用std::vector::at() 而不是为无效索引编写自己的测试? vector::at() 保证会抛出 out_of_range 异常。 ““异常!”cout 永远不会到达。程序在到达该点之前发生故障“ - 你怎么知道程序将到达那个点?也许它去叫backpack.erase @PaulMcKenzie 也许这是一个编码练习。 确实如此。正如我所提到的,我正在自学 C++。因此,当我去学习一个新概念时,我倾向于尝试将新事物分解成块。首先,我专门为整数编写了这段代码。然后我把它变成了一个模板。然后我添加了错误处理和异常抛出。接下来我将使用标准异常,添加特性方法等等。我发现这让我更容易掌握新信息而不会不知所措。不过谢谢你的回复。我很感激! 一些注意事项:索引到容器中的正确数据类型是size_t,而不是unsigned int(尽管在某些实现中它们可能相同)。除非这是为了学习,否则您可能不应该遇到这个异常。修复程序状态没有任何意义。它遇到了意外情况,唯一安全的选择是让它终止。默认情况下,当您遇到未捕获的异常时会发生这种情况。 【参考方案1】:

您的错误检查已关闭 1。

if (backpack.empty() || backpack.size() < b)

如果std::vector 背包只包含两个值,backpack.size() 将是 2,backpack 将包含 backpack[0]backpack[1]

不幸的是,如果索引 b 被传入为 2,此代码仍将尝试访问 backpack[2],从而导致未定义的行为。

其实整个if语句可以简单改写为:

if (b >= backpack.size())
    throw string("Out of Range");

【讨论】:

手掌,见脸。非常感谢。【参考方案2】:

您的代码中有一个“off by one”错误。,

考虑如果数组不为空并且代码中的b == backpack.size() 会发生什么。

 if (backpack.empty() || backpack.size() < b)
            throw string("Out of Range");
 return backpack[b];

在这种情况下,backpack 元素的有效索引是 0backpack.size() - 1

如果b == backpack.size(),代码不会抛出异常,并且会尝试返回backpack[backpack.size()],这会给出未定义的行为。

未定义行为的一个可能症状是“段错误”。

避免该问题的一种方法是将测试更改为backpack.size() &lt;= b

【讨论】:

确实如此。这里的其他一些人已经指出了这个确切的问题。我已经纠正了它。我非常感谢您的反馈和帮助!有时,最简单的疏忽会导致程序出错,不是吗? :)【参考方案3】:

另一种选择是利用std::vector::at(),它将在越界索引上抛出std::out_of_range 异常:

    T operator [] (const unsigned& b)
    
        try 
        
           return backpack.at(b);
        
        catch (std::out_of_range& e)
        
            throw string("Out of Range");
        
    

    void operator -= (const unsigned& b)
    
        try
        
            backpack.at(b);
            backpack.erase(backpack.begin() + b);
        
        catch(std::out_of_range& e)
        
           throw std::string("No such element exists.");
        
    

Live Example

【讨论】:

看起来很棒!我真的很感谢你指出这一点。自学的部分问题是很容易错过这样的小事情。 :)

以上是关于std::vector 段错误而不是抛出异常的主要内容,如果未能解决你的问题,请参考以下文章

如何处理 std::vector 的错误?

Laravel 5刀片在出现错误而不是抛出异常时显示空白页面

在锁 c#2 中抛出异常

Laravel 验证器抛出异常而不是重定向回来

Java-异常处理

ICacheLock 上的 Apache Ignite.NET TryEnter 在网络通信错误时返回 false 而不是抛出异常