You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让value_type构造函数向解析函数标识非致命异常?

问题描述

我正在开发一个需读取10余种不同类型CSV文件的应用,数据将存入std::map或vector等容器。此前每种文件都有独立解析函数,我正将其统一为单个模板函数,以降低后续维护成本并实现统一的损坏文件错误报告。

该函数逐行读取内容,判断容器是否具备key概念(如std::map),对关联容器使用emplace方法,对其他容器(如vector)使用emplace_back方法。容器的value_type类仅需满足能通过CSV行实例化的要求,其构造函数抛出的任何异常都被视为致命错误,程序会报告输入文件名和行号后退出,代码如下:

try {
        if constexpr (is_associative_container<Container>(NULL)) {
                result->emplace(typename Container::key_type(
                    key, keylen), value);
        } else {
                result->emplace_back(value);
        }
} catch (const std::exception &e) {
        fprintf(stderr, "%s:%zd: %s\n", path, line, e.what());
        goto failure;
}

目前该函数已完成约75%的CSV解析工作,但剩余25%的CSV文件处理更复杂:其中部分行需特殊处理,内容不应存入容器。我想知道,value_type的构造函数如何向解析函数标识抛出的异常不应被视为致命异常?使用标准异常(如std::bad_function_call)作为标识的方式不可靠,因为无法保证该异常不会意外出现,还有其他解决方案吗?

解决方案

1. 定义专属的非致命异常类

直接自定义一个用于标记“需跳过该行”的异常类型,彻底区分致命错误和跳过行的场景:

// 自定义异常,仅用于标记当前行无需存入容器
class SkipRowException : public std::exception {
public:
    const char* what() const noexcept override {
        return "Row should be skipped";
    }
};

修改解析函数的catch逻辑,单独处理该异常:

try {
        if constexpr (is_associative_container<Container>(NULL)) {
                result->emplace(typename Container::key_type(key, keylen), value);
        } else {
                result->emplace_back(value);
        }
} catch (const SkipRowException&) {
        // 仅跳过当前行,不触发致命错误流程
        continue;
} catch (const std::exception &e) {
        fprintf(stderr, "%s:%zd: %s\n", path, line, e.what());
        goto failure;
}

在value_type构造函数中,遇到需要跳过的行时直接抛出SkipRowException即可,完全不会和标准异常或其他致命异常混淆。

2. 拆分构造与行过滤逻辑,用可选值替代异常

如果不想用异常控制流程,可以把“行是否保留”的判断从构造函数中拆分出来,为value_type添加静态工厂方法返回std::optional<value_type>:

struct MyValueType {
    // 私有构造函数,仅通过工厂方法创建
    MyValueType(/* 构造参数 */) { /* ... */ }

    static std::optional<MyValueType> from_csv_row(const std::string& row) {
        if (/* 判断该行需要跳过,比如是注释、特殊标记行 */) {
            return std::nullopt;
        }
        // 正常构造对象,构造失败则抛出致命异常
        try {
            return MyValueType(/* 解析row得到的参数 */);
        } catch (...) {
            throw;
        }
    }
};

解析模板函数中先通过工厂方法判断是否保留该行:

// 逐行读取逻辑中...
std::string current_row;
while (getline(file, current_row)) {
    line++;
    auto opt_value = ValueType::from_csv_row(current_row);
    if (!opt_value) {
        continue;
    }

    // 插入容器的逻辑
    try {
            if constexpr (is_associative_container<Container>(NULL)) {
                    result->emplace(typename Container::key_type(key, keylen), *opt_value);
            } else {
                    result->emplace_back(*opt_value);
            }
    } catch (const std::exception &e) {
            fprintf(stderr, "%s:%zd: %s\n", path, line, e.what());
            goto failure;
    }
}

这种方式把“跳过行”的逻辑从错误异常中分离,更符合异常仅用于错误场景的设计原则。

3. 为模板函数添加行过滤回调

如果不同CSV的行过滤规则差异较大,可以给模板解析函数添加可选回调参数,由调用方决定行的保留规则:

template<typename Container>
void parse_csv(const std::string& path, Container* result, 
               std::function<bool(const std::string&)> should_keep_row = [](const auto&){return true;}) {
    // 打开文件等前置逻辑...
    std::string current_row;
    size_t line = 0;
    while (getline(file, current_row)) {
        line++;
        if (!should_keep_row(current_row)) {
            continue;
        }
        // 后续解析、插入容器的逻辑...
    }
}

调用时针对特殊CSV传入过滤规则:

std::vector<MyValueType> vec;
parse_csv("special.csv", &vec, [](const std::string& row) {
    // 示例:跳过注释行和空行
    return !row.starts_with("#") && !row.empty();
});

这种方式让解析函数只负责通用的解析和插入逻辑,过滤逻辑完全由调用方控制,职责更清晰。


内容的提问来源于stack exchange,提问作者Mikhail T.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 13:35:28