有时候会有从数据库或者其他地方拿到一组数据,全是字符串类型,然后需要自己根据实际需要的使用类型去进行转换。(我遇到这个恰好是在mysql查询读出来的数据时,是个二维的字符串数组)。
先引出最简单的一种情况,明确知道将要解析出来的类型和个数。通常就直接如下解决。1
2
3
4std::array<std::string, 2> iDataArray;
iDataArray[0] = "1";
iDataArray[1] = "dandiao";
cout << "1 user_id = " << std::stoi(iDataArray[0]) << ", user_nick = " << iDataArray[1] << endl;
解决方案
现在,假设上面数据中iDataArray里的两个字段实际要转换的类型变了,就需要重新写一段类似的代码。为了解决这个问题,自然想到将参数类型模版化,来达到通用的效果,且看代码:1
2
3
4
5
6
7template <typename T1, typename T2>
void Decode(std::array<std::string, 2>& rikDataArray, T1& paramer1, T2& paramer2)
{
// 这里还要考虑将字符串类型转换成哪一种类型,还好boost提供了一个通用的模板函数
paramer1 = boost::lexical_cast<T1>(rikDataArray[0]);
paramer2 = boost::lexical_cast<T2>(rikDataArray[1]);
}
上面的代码利用了模板参数来传入参数类型和顺序信息,解决了上面提到的问题。这里引入了boost::lexical_cast这个模板函数,它可以将传入的参数以你指定的类型返回。当然我们这里其实都是将字符串转换成指定类型。
调用方式如下:1
2
3
4int nID = 0;
std::string iNickStr;
Decode<int, std::string>(iDataArray, nID, iNickStr);
cout << "2 user_id = " << nID << ", user_nick = " << iNickStr << endl;
但是,上面的方法有个特别明显的缺点:限定了只能解析两个字段。
使用模板推导过程实现通用
对于源数据字段数的限制,用大小可变的容器vector来代替array即可。
对于模板参数的个数,自然想到用c++11的可变参数模版。1
2
3
4
5
6
7
8
9
10
11
12
13template <typename ... Parameters>
bool Decode(std::vector<std::string>& rikDataVector, Parameters... args)
{
if(rikDataVector.empty() || sizeof...(Parameters) != rikDataVector.size())
{
std::cout << "data src not equal cout of paramer" << std::endl;
return false;
}
// 为了避免拷贝std::vector<std::string>,增加一个索引参数记录当前处理到的索引。或者用其他方式传指定索引开始的引用内容进去
size_t nIndex = 0;
return DecodeDetail(rikDataVector, nIndex, args...);
}
代码中函数DecodeDetail是利用了模板推导过程逐个剥离出参数来转换值。相关函数实现为:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32#include <cassert>
template<typename T>
bool DecodeDetail(const std::vector<std::string>& rikDataVector, size_t& rnIndex, T& rValue)
{
assert(rnIndex == rikDataVector.size() - 1);
if(!Convert(rikDataVector[rnIndex], rValue))
{
std::cout << "convert index = " << rnIndex << ", value = " << rikDataVector[rnIndex] << " to type = " << typeid(rValue).name() << " failed";
return false;
}
return true;
}
template<typename T1, typename... Parameters>
bool DecodeDetail(const std::vector<std::string>& rikDataVector, size_t& rnIndex, T1& rValue, Parameters&... args)
{
if(rikDataVector.size() - rnIndex - 1 != sizeof...(Parameters))
{
std::cout << "data src not equal cout of paramer" << std::endl;
return false;
}
if(!Convert(rikDataVector[rnIndex], rValue))
{
std::cout << "convert index = " << rnIndex << ", value = " << rikDataVector[rnIndex].c_str() << " to type = " << typeid(rValue).name() << " failed";
return false;
}
return DecodeDetail(rikDataVector, ++rnIndex, args...);
}
这里代码里出现了一个Convert函数,传入转换源和目标参数,返回转换是否成功。
Convert的两种实现方法
这里其实可以直接使用boost::lexical_cast进行转换来达到效果,我这里之所以封装成函数,是因为有两种方案去实现Convert。
下面是方案1:使用stringstream,加上对string的特例化处理达到效果。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 缺点,对于float类型的字符串“2.2”转换成int类型时,不会报错。
#include <sstream>
template<typename TypeDst, typename TypeSrc>
bool Convert(const TypeSrc& rikSrc, TypeDst& rValue)
{
if(!(std::istringstream(rikSrc) >> rValue))
{
return false;
}
return true;
}
// 模板特例化:解决源字符串中带空格或者回车时的转换问题
template<>
bool Convert(const std::string& rikSrcStr, std::string& rValue)
{
rValue = rikSrcStr;
return true;
}
下面是方案2:封装使用boost::lexical_cast1
2
3
4
5
6
7
8
9
10
11
12
13
14#include <boost/lexical_cast.hpp>
template<typename TypeDst, typename TypeSrc>
bool Convert(const TypeSrc& rikSrc, TypeDst& rValue)
{
try
{
rValue = boost::lexical_cast<TypeDst>(rikSrc);
}
catch(const std::exception& rikException)
{
return false;
}
return true;
}
以上两种Convert的实现方案都有效,其实boost::lexical_cast内部实现就是使用输入输出流,只不过做了更多的情况处理。
测试
是时候测试一下了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44int nInt = 0;
std::string iStr;
float fFloat = 0.0f;
bool bBool = false;
// 测试1: int, string
std::cout << "测试1: int, string ==> " << std::endl;
std::vector<std::string> iDataVector1{"1", "test1"};
if(false != Decode(iDataVector1, nInt, iStr))
{
std::cout << "bBool = " << bBool << ", nInt = " << nInt << ", fFloat = " << fFloat << ", iStr = " << iStr << std::endl;
}
else
{
std::cout << "test1 decode error" << std::endl;
}
std::cout << std::endl;
// 测试2: float, string , int
std::cout << "测试2: float, string , int ==> " << std::endl;
std::vector<std::string> iDataVector2{
"2.222", "test2", "2"};
if(false != Decode(iDataVector2, fFloat, iStr, nInt))
{
std::cout << "bBool = " << bBool << ", nInt = " << nInt << ", fFloat = " << fFloat << ", iStr = " << iStr << std::endl;
}
else
{
std::cout << "test2 decode error" << std::endl;
}
std::cout << std::endl;
// 测试3: string, bool, int, float
std::cout << "测试3: string, bool, int, float ==> " << std::endl;
std::vector<std::string> iDataVector3{"test3", "1", "3", "3.333"};
if(false != Decode(iDataVector3, iStr, bBool, nInt, fFloat))
{
std::cout << "bBool = " << bBool << ", nInt = " << nInt << ", fFloat = " << fFloat << ", iStr = " << iStr << std::endl;
}
else
{
std::cout << "test3 decode error" << std::endl;
}
std::cout << std::endl;
下面是测试结果:1
2
3
4
5
6
7
8测试1: int, string ==>
bBool = 0, nInt = 1, fFloat = 0, iStr = test1
测试2: float, string , int ==>
bBool = 0, nInt = 2, fFloat = 2.222, iStr = test2
测试3: string, bool, int, float ==>
bBool = 1, nInt = 3, fFloat = 3.333, iStr = test3
到了这里似乎已经解决问题了。
应对多维的解析
上面的方案只适用一维的呀,如果是二维的怎么办,只能提供一个容易来包装每组一维数据对应的参数了。比如使用一个结构体对应存放一个一维的解析数据,然后使用std::list来容纳这些结构体。
但是对于不同的形参组合,需要定义不同的结构体,比较麻烦。幸运的是,c++11提供的tuple在这种情况下提供了灵活的方案。
我们先看对于一维的实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#ifdef WIN32 // LINUX
template<typename ... Parameters>
std::tuple<Parameters ...> MakeTuple(Parameters ... args)
{
return std::make_tuple(args...);
}
#endif
template<typename... Parameters>
bool Decode(const std::vector<std::string>& rikDataVector, std::tuple<Parameters...>& riParamerTuple)
{
if(rikDataVector.empty() || sizeof...(Parameters) != rikDataVector.size())
{
std::cout << "data src not equal cout of paramer" << std::endl;
return false;
}
try
{
size_t nIndex = rikDataVector.size() - 1;
#ifndef WIN32 // CentOS7.0下GCC4.8测试正常,其他的没有测试。
riParamerTuple = std::make_tuple(boost::lexical_cast<Parameters>(rikDataVector[nIndex--])...);
#else
riParamerTuple = MakeTuple(boost::lexical_cast<Parameters>(rikDataVector[nIndex--])...);
#endif
// 这里要注意两点:
/* 1、为什么要用--运算符来逆序传入数据源?
考虑int i = 0; printf("%d,%d,%d", i++, i++, i++); 输出2,1,0 。因为默认的函数调用约定函数参数压栈顺序为从右向左,
所以为了确保传入的参数顺序是从第一个开始进行转换,就要使得传入的源数据顺序为 rikDataVector[0],rikDataVector[1],rikDataVector[3]...。
因此要用--。
*/
/* 2、为什么WIN32下要用函数MakeTuple来封装std::make_tuple?(以下测试均在VS2013中)。
因为实际测试中发现,直接使用std::make_tuple和boost::lexical_cast以及解变参模板运算符'...'的组合使用,会存在问题。
比如使用两个参数的测试数据时:
size_t nIndex = rikDataVector.size() - 1; // 这里测试数据是1
std::tuple<Parameters...> iTuple2((boost::lexical_cast<Parameters>(rikDataVector[nIndex--]))...);
// Parameters... = int, string 等效为 lexical_cast<string>(rikDataVector[0]), lexical_cast<int>(rikDataVector[1]) 会异常
// parameters... = string, int 等效为 lexical_cast<string>(rikDataVector[0]), lexical_cast<int>(rikDataVector[1])
// parameters... = int, float 等效为 lexical_cast<float>(rikDataVector[0]), lexical_cast<int>(rikDataVector[1]) 会异常
// parameters... = float, int 等效为 lexical_cast<float>(rikDataVector[0]), lexical_cast<int>(rikDataVector[1])
// parameters... = string, float 等效为 lexical_cast<string>(rikDataVector[0]), lexical_cast<float>(rikDataVector[1])
// parameters... = float, string 等效为 lexical_cast<string>(rikDataVector[0]), lexical_cast<float>(rikDataVector[1]) 会异常
没有去测试类型更多的结果,但是当我用如下Print函数类型推导输出全部参数时又没有问题。
// 打印变参模板形参值
template<typename T1>
void Print(T1&& rnValue)
{
std::cout << rnValue << std::endl;
}
template<typename T1, typename... Args>
void Print(T1&& rnValue, Args&&... args) // int, float, string
{
Print(rnValue);
Print(args...);
}
Print((boost::lexical_cast<Parameters>(rikDataVector[nIndex--]))...); // 没有问题。
综合比较:
Print((boost::lexical_cast<Parameters>(rikDataVector[nIndex--]))...);
std::tuple<Parameters...> iTuple2((boost::lexical_cast<Parameters>(rikDataVector[nIndex--]))...);
区别就是Print是我自定义的函数,tuple()是标准库函数。没有找到为什么会不同的原因。
但能想到也用一个函数去封装std::make_tuple,因此才有了MakeTuple函数。
// 附带MakeTuple测试结果:
// Parameters... = int, string 等效为 lexical_cast<int>(rikDataVector[0]), lexical_cast<string>(rikDataVector[1])
// parameters... = string, int 等效为 lexical_cast<string>(rikDataVector[0]), lexical_cast<int>(rikDataVector[1])
// parameters... = int, float 等效为 lexical_cast<int>(rikDataVector[0]), lexical_cast<float>(rikDataVector[1])
// parameters... = float, int 等效为 lexical_cast<float>(rikDataVector[0]), lexical_cast<int>(rikDataVector[1])
// parameters... = string, float 等效为 lexical_cast<string>(rikDataVector[0]), lexical_cast<float>(rikDataVector[1])
// parameters... = float, string 等效为 lexical_cast<float>(rikDataVector[0]), lexical_cast<string>(rikDataVector[1])
当然!还有另一种解决方法,就是用一个函数包装lexical_cast的转换,包装代码如下:
template<typename TypeDst, typename TypeSrc>
TypeDst Convert(const TypeSrc& rikSrc)
{
return boost::lexical_cast<TypeDst>(rikSrc);
}
*/
}
catch(const std::exception& rikException)
{
std::cout << "decode failed when user boost::lexical_cast and tuple" << std::endl;
return false;
}
return true;
}
很抱歉这段代码里有那么多的注释,只是我想更好的去描述这个’VS编译器的bug’:make_tuple和lexical_cast配合变参模板解包一起使用时,会出问题!
如果你不想看上面那么一大堆的注释,我这里给你总结一下,注意以下两点:
- 注意函数参数的压栈顺序是从右向左的,所以要使用从后向前取数据源。
- 注意WINDOWS下,VS编译器下,make_tuple和lexical_cast配合变参模板解包一起使用时,其转换类型顺序无规律可寻,就是有问题。可以使用函数包装make_tuple或者lexical_cast来解决这个问题!
其实除去注释部分,代码量只有区区几行而已。如果是GCC编译器,甚至连上面的MakeTuple都不需要。怎么样,够优雅了吧?
我们看看测试代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57// 测试4: bool, int, float, string
std::cout << "测试4: bool, int, float, string ==> " << std::endl;
std::vector<std::string> iDataVector4{"0", "4", "4.444", "test4"};
std::tuple<bool, int, float, std::string> iParamerTuple4;
if(false != Decode(iDataVector4, iParamerTuple4))
{
std::cout << "tuple<0> = " << std::get<0>(iParamerTuple4) << ", tuple<1> = " << std::get<1>(iParamerTuple4)
<< ", tuple<2> = " << std::get<2>(iParamerTuple4) << ", tuple<3> = " << std::get<3>(iParamerTuple4) << std::endl;
}
else
{
std::cout << "test4 decode error" << std::endl;
}
std::cout << std::endl;
// 测试5: float, string, int,
std::cout << "测试5: float, string, int ==> " << std::endl;
std::vector<std::string> iDataVector5{"5.55", "test5", "555"};
std::tuple<float, std::string, int> iParamerTuple5;
if(false != Decode(iDataVector5, iParamerTuple5))
{
std::cout << "tuple<0> = " << std::get<0>(iParamerTuple5) << ", tuple<1> = " << std::get<1>(iParamerTuple5)
<< ", tuple<2> = " << std::get<2>(iParamerTuple5) << std::endl;
}
else
{
std::cout << "test5 decode error" << std::endl;
}
std::cout << std::endl;
// 测试6: string, int,
std::cout << "测试6: string, int ==> " << std::endl;
std::vector<std::string> iDataVector6{"test6", "66"};
std::tuple<std::string, int> iParamerTuple6;
if(false != Decode(iDataVector6, iParamerTuple6))
{
std::cout << "tuple<0> = " << std::get<0>(iParamerTuple6) << ", tuple<1> = " << std::get<1>(iParamerTuple6) << std::endl;
}
else
{
std::cout << "test6 decode error" << std::endl;
}
std::cout << std::endl;
// 测试7: int, string
std::cout << "测试7: int, string ==> " << std::endl;
std::vector<std::string> iDataVector7{"77", "test7"};
std::tuple<int, std::string> iParamerTuple7;
if(false != Decode(iDataVector7, iParamerTuple7))
{
std::cout << "tuple<0> = " << std::get<0>(iParamerTuple7) << ", tuple<1> = " << std::get<1>(iParamerTuple7) << std::endl;
}
else
{
std::cout << "test7 decode error" << std::endl;
}
std::cout << std::endl;
特地多测试了几组数据,因为之前测试情况少了出了很大问题!结果为:1
2
3
4
5
6
7
8
9
10
11测试4: bool, int, float, string ==>
tuple<0> = 0, tuple<1> = 4, tuple<2> = 4.444, tuple<3> = test4
测试5: float, string, int ==>
tuple<0> = 5.55, tuple<1> = test5, tuple<2> = 555
测试6: string, int ==>
tuple<0> = test6, tuple<1> = 66
测试7: int, string ==>
tuple<0> = 77, tuple<1> = test7
二维的解析
是时候上二维解析的代码啦,有了上面一维的,二维的就很简单了,这里连着测试代码一起放上吧:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35// 注意:请自行确保rikData2DVector中每个一维数组中要解析的类型顺序是相同的。
template<typename... Parameters>
bool Decode(const std::vector<std::vector<std::string>>& rikData2DVector, std::vector<std::tuple<Parameters...>>& riParamerTupleVector)
{
for(size_t i = 0; i < rikData2DVector.size(); ++i)
{
std::tuple<Parameters...> iTmpTuple;
if(!Decode(rikData2DVector[i], iTmpTuple))
{
std::cout << "data src group[" << i << "] decode error!" << std::endl;
return false;
}
riParamerTupleVector.push_back(iTmpTuple);
}
return true;
}
// 测试8: {int, string},{int,string}{int, string}
std::cout << "测试8: {int, string},{int,string}{int, string} ==> " << std::endl;
std::vector<std::vector<std::string>> iData2DVector8{{"881", "test81"}, {"882", "test82"}};
std::vector<std::tuple<int, std::string>> iParamerTupleVector8;
if(false != Decode(iData2DVector8, iParamerTupleVector8))
{
for(const std::tuple<int, std::string>& rikParamerTuple : iParamerTupleVector8)
{
std::cout << "tuple<0> = " << std::get<0>(rikParamerTuple) << ", tuple<1> = " << std::get<1>(rikParamerTuple) << std::endl;
}
}
else
{
std::cout << "test8 decode error" << std::endl;
}
std::cout << std::endl;
测试结果为:1
2
3测试8: {int, string},{int,string}{int, string} ==>
tuple<0> = 881, tuple<1> = test81
tuple<0> = 882, tuple<1> = test82
嗯,因为二维就是对一维的重复调用,所以这里测试代码我就从简啦。
总结
本来要讲解的很少的,但是因为VS编译器的那个问题,分析测试了好久,然后又多了使用对lexical_cast模板的模拟实现等,这篇博客就痈肿起来了。
不过总算要终结啦,考虑到这份代码主要是讲解这个思路,还有待优化的地方,并且复用性也不是特别大,所以就不上传github了。这里直接贴上吧。
1 | #include <iostream> |