添加配置项,允许从配置文件中(yaml格式)中加载配置。提供了大量类型转换类的偏特化实现,
boost库中的lexical_cast
安装命令:apt install libboost-dev
yaml-cpp GitHub - jbeder/yaml-cpp: A YAML parser and emitter in C++
虚基类,是配置变量的基类。
构造函数传入配置参数名称和配置参数描述(配置参数名称不区分大小写)。
成员变量有配置参数的名称和配置参数的描述。
类内有纯虚函数toString(转换成字符串),fromString(从字符串初始化值),getTypeName(返回配置参数值的类型名称)。
class ConfigVarBase {
public:
typedef std::shared_ptr ptr;
/**
* @brief 构造函数
* @param[in] name 配置参数名称[0-9a-z_.]
* @param[in] description 配置参数描述
*/
ConfigVarBase(const std::string &name, const std::string &description = "")
: m_name(name)
, m_description(description) {
std::transform(m_name.begin(), m_name.end(), m_name.begin(), ::tolower);
}
/**
* @brief 析构函数
*/
virtual ~ConfigVarBase() {}
/**
* @brief 返回配置参数名称
*/
const std::string &getName() const { return m_name; }
/**
* @brief 返回配置参数的描述
*/
const std::string &getDescription() const { return m_description; }
/**
* @brief 转成字符串
*/
virtual std::string toString() = 0;
/**
* @brief 从字符串初始化值
*/
virtual bool fromString(const std::string &val) = 0;
/**
* @brief 返回配置参数值的类型名称
*/
virtual std::string getTypeName() const = 0;
protected:
/// 配置参数的名称
std::string m_name;
/// 配置参数的描述
std::string m_description;
};
什么是仿函数?
在C++编程语言中,仿函数(Functor)是一种行为类似于函数的对象,可以像函数一样被调用。仿函数是一个类或结构体,它重载了圆括号操作符 "()",因此可以像函数一样被调用。仿函数可以在算法中使用,例如std::transform(),可以将一个序列中的每个元素映射到另一个序列中。仿函数的好处是可以通过重载操作符来实现更加灵活的行为,例如可以在一个仿函数中保存一些状态信息,或者将它作为一个回调函数来传递给其他函数使用。
首先写一个类型转换模板类,基于boost库中的lexical_cast实现:
template
class LexicalCast {
public:
/**
* @brief 类型转换
* @param[in] v 源类型值
* @return 返回v转换后的目标类型
* @exception 当类型不可转换时抛出异常
*/
T operator()(const F &v) {
return boost::lexical_cast(v);
}
};
什么是模板类的偏特化?
类模板的全特化不必为所有模板参数提供实参。我们可以只指定一部分而非所有模板参数,或是参数的一部分而非全部特性。 一个类模板的偏特化本身是一个模板,使用它时用户还必须为那些在特例化版本中未指定的模板参数提供实参。
· 模板类偏特化举例(yaml格式的字符串抓换成vector
template
class LexicalCast> {
public:
std::vector operator()(const std::string &v) {
YAML::Node node = YAML::Load(v);
typename std::vector vec;
std::stringstream ss;
for (size_t i = 0; i < node.size(); ++i) {
ss.str("");
ss << node[i];
vec.push_back(LexicalCast()(ss.str()));
}
return vec;
}
};
这样子,假如有一个语句,LexicalCast
除此之外,还有yaml字符串与vector,list,set,unordered_set,map,unordered_map的偏特化,以及反过来转换的偏特化类。
为了避免歧义,模板中需要在使用模板类的类型成员前加上typename(比如typename std::vector
这是一个模板类,它继承自ConfigVarBase类,重写了上面的纯虚函数。功能是配置参数木板子类,保存对应的类型的参数值,模板参数的T是参数的具体类型。
类的构造函数传入参数名称,参数的默认值和参数描述(这里的参数值的类型为T)。
成员变量有T(参数值),以及一个map。键为一个整数,值为一个function,function存一个返回值为void,传入两个字符串(一个新参数值,一个旧参数值)的成员函数。
为什么要用map?
为了在调用回调函数的时候可以按照顺序输出,插入和删除的时间复杂度也比较合理。
FromStr是从std::string转换成T类型的仿函数
ToStr是从T转换成std::string的仿函数
toString成员函数使用ToStr将m_val(参数值)转换成yaml字符串
fromString成员函数使用FromStr将val(yaml字符串)转换成参数值,并且存储到对象中。
getValue函数和SetValue函数用于获取当前参数的值。设置当前参数的值的时候,若参数的值有变化,需要调用全部注册的回调函数(若设置后的参数值无变化,就不调用回调函数了),然后再修改参数值。(这里为了提高性能,在调用回调函数的时候只上读锁,在赋值时才用写锁)。
getTypeName函数用于返回参数值的类型名称。
addListener函数用于向map中添加回调函数,每个回调函数都有唯一的id(id在函数中用静态整数变量实现)。
delListener用于删除回调函数,传入回调函数的id。
getListener用于返回指定回调函数的function。
clearListener用于删除全部回调函数。
Config是ConfigVar的管理类,用来管理全部的ConfigVar全特化后的对象。他通过单例模式进行管理,整个服务进程只有一个对象。
没有任何静态成员变量,通过GetDatas函数内的静态的map进行管理,以及用GetMutex函数内的静态的锁加解锁。
Lookup函数用于创建或获取对应参数名的配置参数。传入三个参数,字符串形式的配置参数名称,T类型的参数默认值,字符串形式的参数描述。首先判断是否存在参数名称的配置,若存在,则从map中取出这个配置项(ConfigVarBase类型的指针,指向了ConfigVar的某个全特化的类的对象)。并且通过dynamic_pointer_cast,转换成ConfigVar
还有一个重载的Lookup函数用于查找配置参数,传入配置参数名称,返回对应配置参数名称的配置参数。若不存在或者类型错误的话,都会返回nullptr。
LoadFromYaml函数用于使用yaml初始化配置模块,参数传入一个yaml对象。函数内有名为ListAllMember的函数,采用类似遍历二叉树的方式遍历。
LoadFromConfDir用于加载path文件夹里面的配置文件。
LookupBase用于返回配置参数的基类(他跟上面的Lookup的区别是一个返回ConfigVar
Visit函数用于遍历配置模块里面所有配置项,并且将配置项作为传入函数的参数进行执行。
class Config {
public:
typedef std::unordered_map ConfigVarMap;
typedef RWMutex RWMutexType;
template
static typename ConfigVar::ptr Lookup(const std::string &name,
const T &default_value, const std::string &description = "") {
RWMutexType::WriteLock lock(GetMutex());
auto it = GetDatas().find(name);
if (it != GetDatas().end()) {
auto tmp = std::dynamic_pointer_cast>(it->second);
if (tmp) {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Lookup name=" << name << " exists";
return tmp;
} else {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name=" << name << " exists but type not "
<< TypeToName() << " real_type=" << it->second->getTypeName()
<< " " << it->second->toString();
return nullptr;
}
}
if (name.find_first_not_of("abcdefghikjlmnopqrstuvwxyz._012345678") != std::string::npos) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name invalid " << name;
throw std::invalid_argument(name);
}
typename ConfigVar::ptr v(new ConfigVar(name, default_value, description));
GetDatas()[name] = v;
return v;
}
template
static typename ConfigVar::ptr Lookup(const std::string &name) {
RWMutexType::ReadLock lock(GetMutex());
auto it = GetDatas().find(name);
if (it == GetDatas().end()) {
return nullptr;
}
return std::dynamic_pointer_cast>(it->second);
}
/**
* @brief 使用YAML::Node初始化配置模块
*/
static void LoadFromYaml(const YAML::Node &root);
/**
* @brief 加载path文件夹里面的配置文件
*/
static void LoadFromConfDir(const std::string &path, bool force = false);
/**
* @brief 查找配置参数,返回配置参数的基类
* @param[in] name 配置参数名称
*/
static ConfigVarBase::ptr LookupBase(const std::string &name);
/**
* @brief 遍历配置模块里面所有配置项
* @param[in] cb 配置项回调函数
*/
static void Visit(std::function cb);
private:
/**
* @brief 返回所有的配置项
*/
static ConfigVarMap &GetDatas() {
static ConfigVarMap s_datas;
return s_datas;
}
/**
* @brief 配置项的RWMutex
*/
static RWMutexType &GetMutex() {
static RWMutexType s_mutex;
return s_mutex;
}
};
使用举例:
sylar::ConfigVar::ptr g_int =
sylar::Config::Lookup("global.int", (int)8080, "global int");
// 获取/创建新的配置项,配置参数名称为global.int,
//参数默认值为int类型的8080,参数描述为global int,
//若参数存在则返回指向配置项的指针
sylar::ConfigVar>::ptr g_int_unordered_set =
sylar::Config::Lookup("global.int_unordered_set", std::unordered_set{1, 2, 3}, "global int unordered_set");
注意:类模板的声明和定义必须写在同一个文件里,因为函数模板实际上并不是一个真正的函数。它仅仅是编译器用来生成函数或类的一张“图纸”。模板不会占用内存,最终生成的函数才会占用内存。函数模板只是用来告诉编译器,在遇到某个函数调用的时候需要生成什么样子的函数。在编译过程中,编译器会参考函数模板,然后生成对应的真正的函数。(这是一个我在写米哈游笔试的时候遇到的巨坑)