ZLToolKit模块(二)CMD

文章介绍了ZLToolKit中的命令行解析模块,支持命令简写、默认值等功能。通过CMD_http类展示了如何使用OptionParser进行选项解析,包括添加选项、回调处理等。同时,文章讨论了C++11中的forward和emplace优化,解释了emplace如何避免一次额外的移动构造操作,提高效率。

1. CMD功能描述

ZLToolKit的命令行解析模块。

  1. 支持常见的命令行解析功能,如--help, 命令简写形式,如-h
  2. 示例代码在test_shell.cpp

2. 使用示例

  • http测试客户端
class CMD_http: public CMD 
{
public:
    CMD_http() {
        _client.reset(new TestClient);
        _parser.reset(new OptionParser([this](const std::shared_ptr<ostream> &stream,mINI &args){
            //所有选项解析完毕后触发该回调,我们可以在这里做一些全局的操作
            if(hasKey("connect")){
                //发起连接操作
                connect(stream);
                return;
            }
            if(hasKey("commit")){
                commit(stream);
                return;
            }
        }));

        (*_parser) << Option('s',/*该选项简称,如果是\x00则说明无简称*/
                             "server",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/
                             Option::ArgRequired,/*该选项后面必须跟值*/
                             "139.224.212.51:8080",/*该选项默认值*/
                             false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
                             "tcp服务器地址,以冒号分隔端口号",/*该选项说明文字*/
                             [this](const std::shared_ptr<ostream> &stream, const string &arg){/*解析到该选项的回调*/
                                 if(arg.find(":") == string::npos){
                                     //中断后续选项的解析以及解析完毕回调等操作
                                     throw std::runtime_error("\t地址必须指明端口号.");
                                 }
                                 //如果返回false则忽略后续选项的解析
                                 return true;
                             });

        (*_parser) << Option('d', "disconnect", Option::ArgNone, nullptr ,false, "是否断开连接",
                             [this](const std::shared_ptr<ostream> &stream, const string &arg){
                                 //断开连接操作,所以后续的参数我们都不解析了
                                 disconnect(stream);
                                 return false;
                             });

        (*_parser) << Option('c', "connect", Option::ArgNone, nullptr, false, "发起tcp connect操作", nullptr);
        (*_parser) << Option('m', "method", Option::ArgRequired, "GET",false, "HTTP方法,譬如GET、POST", nullptr);
        (*_parser) << Option('p', "path", Option::ArgRequired, "/index/api/listRtpServer",false, "HTTP url路径", nullptr);
        (*_parser) << Option('C', "commit", Option::ArgNone, nullptr, false, "提交HTTP请求", nullptr);

    }

    ~CMD_http() {}

    const char *description() const override {
        return "http测试客户端";
    }

private:
    void connect(const std::shared_ptr<ostream> &stream){
        (*stream) << "connect操作" << endl;
        _client->connect(splitedVal("server")[0],splitedVal("server")[1], 30);
    }
    void disconnect(const std::shared_ptr<ostream> &stream){
        (*stream) << "disconnect操作" << endl;
        _client->disconnect();
    }
    void commit(const std::shared_ptr<ostream> &stream){
        (*stream) << "commit操作" << endl;
        _client->commit((*this)["method"],(*this)["path"],(*this)["server"]);
    }

private:
    TestClient::Ptr _client;
};

3. 源码分析

3.1 类图

请添加图片描述


3.2 源码剖析

  1. Option
  • 选项类,存储一条选项相关信息。包括短选项名、长选项名、选项额外参数类型(无参、有参、可选参)、选项额外参数默认值、该选项是否必须给额外参数赋值、选项描述信息、选项对应回调。
  • -在构造函数里默认添加一个helper选项,可以打印出支持的所有选项。
  1. OptionParser
  • 选项解析器。每个命令(CMD)都有一个选项解析器成员对象,OptionParser内部使用map 类型的成员存储了该命令支持的所有选项信息。
  • 用户输入的命令信息最终会通过CMD类调用到该类进行解析,在map中查找option执行其回调函数。
  1. CMD
  • 命令类,继承自map,每次调用operator()操作后,CMD本身存储的是用户当前输入的选项以及非用户输入但具有默认值的选项,而该命令支持的所有选项,存储在_parser中(OptionParser)的_map_options中。
  1. CMDRegister
  • 命令管理单例类。
  • 所有的注册,查找,匹配和运行的入口。

4. C++11

4.1 forward到底做了什么?

    1. 虽然名字叫“转发”(forward),但它本质上就是一个条件 static_cast
      在代码中(非模板函数场景):
std::forward<Option>(option)
等价于:
static_cast<Option&&>(option)
    1. 强制转右值
 OptionParser &operator<<(Option &&option) {
    int index = 0xFF + (int) _map_options.size();
    if (option._short_opt) {
        _map_char_index.emplace(option._short_opt, index);
    }
    // 如果没有forward转发,那么option的类型就是Option,而不是Option&&
    _map_options.emplace(index, std::forward<Option>(option));
    return *this;
}
// 原始模板
template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) {
    return static_cast<_Tp&&>(__t);
}

// 代入 _Tp = Option 后:
// 1. remove_reference<Option>::type 就是 Option
// 2. 参数类型变成 Option& __t (这正好能接收你的左值 option)
// 3. 返回类型 _Tp&& 变成 Option&& (右值引用)

constexpr Option&& forward(Option& __t) {
    return static_cast<Option&&>(__t); // 强制转为右值引用
}
    1. 配合模板类型T&&可以实现完美转发(保留其左值/右值属性)

假设我们有一个包装函数:

template<typename T>
void wrapper(T&& arg) {
    // 这里的 T&& 是万能引用,既能接左值,也能接右值
    process(std::forward<T>(arg)); // 正确做法
    // process(std::move(arg));    // 错误做法(危险!)
}

对完美转发的原理不理解的朋友,可以看看下面链接:
完美转发的详细讲解

4.2 emplace到底优化了什么?

_map_options.emplace(index, std::forward<Option>(option)); //一次移动构造
_map_options.insert(std::make_pair(index, std::forward<Option>(option))); //两次移动构造

在上面的代码中,

  • map::insert 函数时会先调用 std::make_pair 函数创建一个临时 pair 对象,再调用 pair 的移动构造函数将临时 pair 对象移动到 map 中。
  • map::emplace 函数时,则在 map 中分配空间直接构造元素,避免了一次移动操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值