You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在使用字符串键的unordered_map中捕获拼写错误?

如何在使用字符串键的unordered_map中捕获拼写错误?

这确实是个很常见的痛点——用字符串当键虽然灵活,但拼写错误简直是隐形炸弹,编译器和linter都查不出来,debug起来头大。不过确实有不少成熟的办法来解决这个问题,我给你整理几个实用的方案:


1. 用强类型枚举替代手写字符串键(最推荐)

把所有可能的属性键定义成枚举类,再通过固定的函数把枚举转换成对应字符串。这样一来,你打错枚举名称的话,编译器会直接报错,从根源上避免拼写问题。而且还能封装一个安全的取值函数,防止键不存在时的静默错误。

示例代码:

#include <iostream>
#include <string>
#include <unordered_map>
#include <stdexcept>

using namespace std;

// 把所有可能的属性键定义为枚举
enum class PropKey {
    Fine,
    Dandy
};

// 枚举转固定字符串的映射函数
string keyToString(PropKey key) {
    switch(key) {
        case PropKey::Fine: return "fine";
        case PropKey::Dandy: return "dandy";
        default: throw invalid_argument("未知的属性键");
    }
}

typedef unordered_map<string, float> Description;

// 安全的属性获取函数:用枚举当参数,找不到键直接抛异常
float getSafeProp(const Description& desc, PropKey key) {
    const string strKey = keyToString(key);
    auto it = desc.find(strKey);
    if (it == desc.end()) {
        throw runtime_error("未找到属性: " + strKey);
    }
    return it->second;
}

void DoSomething(float how, string ActionName) {
    if (how < 0.1) 
        cout << ActionName << " was done " << endl;
    else if(how >= 0.1 && how <0.3) 
        cout << ActionName << " was done with more passion" << endl;
    else 
        cout << ActionName << " was done HARD" << endl;
}

int main() {
    Description objectA = {
        {keyToString(PropKey::Fine), 0.2},
        {keyToString(PropKey::Dandy), 0.6}
    };

    // 这里如果写PropKey::Fnie,编译器直接报错
    DoSomething(getSafeProp(objectA, PropKey::Fine), "screaming");
    DoSomething(getSafeProp(objectA, PropKey::Dandy), "scheming");

    return 0;
}

优点:编译器直接拦截枚举拼写错误,取值时能明确抛出键不存在的异常,不会像[]运算符那样默默插入默认值(这也是unordered_map的常见坑)。


2. 预定义字符串常量,禁止手写字面量

如果不想改枚举,也可以把所有属性键定义成全局const string常量,所有地方都用常量名而不是直接写字符串。这样常量名拼写错的话,编译器会提示“未定义标识符”。

示例代码:

#include <iostream>
#include <string>
#include <unordered_map>
#include <stdexcept>

using namespace std;

// 预定义所有属性键的常量
const string PROP_FINE = "fine";
const string PROP_DANDY = "dandy";

typedef unordered_map<string, float> Description;

// 安全取值函数,避免[]的隐式插入
float safeGet(const Description& desc, const string& key) {
    auto it = desc.find(key);
    if (it == desc.end()) {
        throw runtime_error("未找到属性: " + key);
    }
    return it->second;
}

void DoSomething(float how, string ActionName) {
    if (how < 0.1) 
        cout << ActionName << " was done " << endl;
    else if(how >= 0.1 && how <0.3) 
        cout << ActionName << " was done with more passion" << endl;
    else 
        cout << ActionName << " was done HARD" << endl;
}

int main() {
    Description objectA = {
        {PROP_FINE, 0.2},
        {PROP_DANDY, 0.6}
    };

    // 用常量名,打错的话编译器直接报错
    DoSomething(safeGet(objectA, PROP_FINE), "screaming");
    DoSomething(safeGet(objectA, PROP_DANDY), "scheming");

    return 0;
}

优点:改动最小,不需要重构现有代码结构,适合快速改造现有项目。


3. 自定义封装类,完全隐藏底层map

如果要彻底杜绝拼写错误的可能,可以自己写一个Description类,把unordered_map封装在内部,只允许通过预定义的接口(枚举或合法键)来设置/获取属性。

示例代码:

#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <stdexcept>

using namespace std;

class Description {
private:
    unordered_map<string, float> props;
    // 维护合法键的集合
    static const unordered_set<string> validKeys;

public:
    // 用枚举作为键的安全设置/获取方法
    enum class Key { Fine, Dandy };

    void setProp(Key key, float value) {
        string strKey;
        switch(key) {
            case Key::Fine: strKey = "fine"; break;
            case Key::Dandy: strKey = "dandy"; break;
            default: throw invalid_argument("未知的属性键");
        }
        props[strKey] = value;
    }

    float getProp(Key key) const {
        string strKey;
        switch(key) {
            case Key::Fine: strKey = "fine"; break;
            case Key::Dandy: strKey = "dandy"; break;
            default: throw invalid_argument("未知的属性键");
        }
        auto it = props.find(strKey);
        if (it == props.end()) {
            throw runtime_error("属性未设置: " + strKey);
        }
        return it->second;
    }

    // 支持动态添加合法键的接口(如果需要运行时新增属性)
    void registerNewKey(const string& newKey) {
        // 这里可以扩展逻辑,比如检查是否已存在等
        const_cast<unordered_set<string>&>(validKeys).insert(newKey);
    }
};

// 初始化合法键集合
const unordered_set<string> Description::validKeys = {"fine", "dandy"};

void DoSomething(float how, string ActionName) {
    if (how < 0.1) 
        cout << ActionName << " was done " << endl;
    else if(how >= 0.1 && how <0.3) 
        cout << ActionName << " was done with more passion" << endl;
    else 
        cout << ActionName << " was done HARD" << endl;
}

int main() {
    Description objectA;
    objectA.setProp(Description::Key::Fine, 0.2);
    objectA.setProp(Description::Key::Dandy, 0.6);

    // 枚举拼写错误会直接触发编译器报错
    DoSomething(objectA.getProp(Description::Key::Fine), "screaming");
    DoSomething(objectA.getProp(Description::Key::Dandy), "scheming");

    return 0;
}

优点:最安全的方案,完全封装了底层实现,外部只能通过合法接口操作属性,任何非法操作要么编译报错,要么运行时抛出明确错误。


额外小技巧

  • 永远不要直接用unordered_map[]运算符取值:如果键不存在,它会自动插入一个默认值0.0,默默修改你的map还让你误以为键存在,用find()或自定义的安全函数替代。
  • Debug模式下加断言:在取值时断言键存在,这样调试时会立刻触发错误,Release模式可以关闭断言不影响性能。
  • 动态键场景:如果需要运行时新增属性,可以维护一个全局的合法键注册表,每次新增键先注册,操作时检查是否在注册表中,避免手敲字符串的拼写错误。

内容来源于stack exchange

火山引擎 最新活动