利用单例模式实现配置文件读取
0 废话
第二篇博客,最近自己看了一下程序设计的23种模式,对其中的单例模式比较感兴趣。想着自己以前程序里面经常要读取配置文件,并且要全局使用,单例模式在此大有用处,于是抽空写了一个,记录一下学习的过程,哈哈。
源码放在我的个人github。
1 单例模式
单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
单例模式本身有很多可以琢磨的地方,感兴趣的可以上CSDN搜博客,此处不详细举例。
2 配置文件读取
single.h 头文件
#ifndef SINGLE_H_
#define SINGLE_H_
#ifndef WIN32
#define CONFIGINFO_DLLAPI
#else
#ifdef ConfigInfo_EXPORTS
#define CONFIGINFO_DLLAPI __declspec(dllexport)
#else
#define CONFIGINFO_DLLAPI __declspec(dllimport)
#endif
#endif
#include <stdio.h>
#include <string>
#include <memory>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <map>
class CONFIGINFO_DLLAPI ConfigInfo
{
public:
using Ptr = std::shared_ptr<ConfigInfo>;
private:
ConfigInfo() {}
ConfigInfo(ConfigInfo &&) = delete;
ConfigInfo(const ConfigInfo &) = delete;
ConfigInfo &operator=(ConfigInfo &&) = delete;
ConfigInfo &operator=(const ConfigInfo &) = delete;
static Ptr config_info;
std::map<std::string, std::string> storage;
public:
~ConfigInfo() {}
public:
static Ptr GetInstance();
bool open(const char *config_file_path);
template <typename T>
T get(std::string key)
{
transform(key.begin(), key.end(), key.begin(), ::tolower);
if (storage.count(key) > 0)
{
double value = stod(storage[key]);
return static_cast<T>(value);
}
else
{
std::cout << "[-CONFIG-] Warning! The key of \" " << key << "\" does not exist, return a default value" << std::endl;
std::cout << "Press enter to continue" << std::endl;
getchar();
return T(0x0);
}
}
};
template <>
std::string ConfigInfo::get<std::string>(std::string key)
{
transform(key.begin(), key.end(), key.begin(), ::tolower);
if (storage.count(key) > 0)
{
return (storage[key]);
}
else
{
std::cout << "[-CONFIG-] Warning! The key of \" " << key << "\" does not exist, return a default value" << std::endl;
std::cout << "Press enter to continue" << std::endl;
getchar();
return "";
}
}
#endif /* !SINGLE_H_ */
single.cpp 文件
#include "Single.h"
#include <fstream>
#include <string>
#include <sstream>
#include <algorithm>
using namespace std;
ConfigInfo::Ptr ConfigInfo::config_info(new ConfigInfo());
/**
* @brief 初始化函数
* @note
* @param *config_file_path: 配置文件路径
* @retval None
*/
bool ConfigInfo::open(const char *config_file_path)
{
ifstream ifs_in(config_file_path, ios::in);
if (!ifs_in)
{
cout << "Config File Lost" << endl;
return false;
}
while (!ifs_in.eof())
{
string line, key, value;
getline(ifs_in, line);
if (line.size() == 0)
{
continue;
}
else if (line.substr(0, 1) == "#" || line.substr(0, 1) == "*")
{
continue;
}
stringstream sstr_line(line);
transform(key.begin(), key.end(), key.begin(), ::tolower);
sstr_line >> key;
sstr_line >> value;
storage[key] = value;
}
return true;
}
/**
* @brief 获取单例模式下的唯一对象
* @note
* @retval
*/
ConfigInfo::Ptr ConfigInfo::GetInstance()
{
return config_info;
}
3 配置文件的全局使用
测试代码 config.txt文件是自己定义的键值对形式配置文件,即
key1 value1
key2 value2
#include "Single.h"
int main(int argc, char *argv[])
{
ConfigInfo::Ptr getconfig = ConfigInfo::GetInstance();
getconfig->open("../config.txt");
std::cout << getconfig->get<int>("num") << std::endl;
std::cout << getconfig->get<double>("start") << std::endl;
std::cout << getconfig->get<std::string>("path") << std::endl;
std::cout << getconfig->get<double>("Angle") << std::endl;
std::cout << getconfig->get<double>("haha") << std::endl;
return 0;
}
4 结语
完成这样一个类,以后访问配置信息定义一个指针就可以自由访问了,一下子就方便了,不再为无法随时随地获取配置信息纠结,不过要记得使用前一定要open自己的配置文件。