曾经曾在一个项目中碰到过一个挺简单的问题,但一时又不能用普通常规的方法去非常好的解决,最后通过C++模板的活用,通过traits相对照较巧妙的攻克了这个问题。本文主要想重现问题发生,若干解决方式的比較,以及最后怎样去解决的过程,或许终于的方案也并非最好的方案,但至少个人认为从发现到思考到解决到改善,这是一个对帮助个人成长非常不错的过程,所以凭记忆想把它记录下来,分享给大家。

先描写叙述下问题,项目中有这样一个接口类会暴露给外部使用,接口类定义例如以下(类方法名称以及描写叙述该问题无关的内容会有所改动、省略或删除):
class IContainer
{
public:
virtual RESULT Insert(const std::string& key, const ExportData& data) = 0;
virtual RESULT Delete(const std::string& key) = 0;
virtual RESULT Find(const std::string& key, ExportData& data) = 0;
};
从内容和名称非常easy看出该接口不外乎一个容器,能够进行增、改、查操作,太简单了,这种接口类会有什么样的问题呢?从接口类的方法中能够发如今Insert和Find方法中都有一个数据类型ExportData,分别作为输入与输出,而如今有这种需求:
1. ExportData须要仅支持整型(long),浮点型(double),字符串(string)以及二进制(void*, size)4种类型的操作(不支持int或float)
2. ExportData须要考虑结构的尺寸,尽量降低空间冗余
3. 即使对以上4种不同数据类型进行操作,还是希望在从ExportData中Get或Set真实数据时,使用的方法能统一
4. 当调用者尝试使用了以上4种类型以外的数据类型时,能通过返回错误让调用方知道类型不匹配

需求描写叙述完成,怎么做?怎样去定义和实现ExportData?是不是非常easy,第一感觉立即就能解决这个问题,并且有n种方法。

第一种方案,为ExportData定义GetData和SetData方法,而且为4种类型分别重载方法,代码例如以下:
class ExportData
{
public:
long GetData() {
return m_lData;
}
void SetData(long data) {
m_lData = data;
}
string GetData() {
return m_strData;
}
void SetData(string data) {
m_strData = data;
}
// ...... overload the other two types private:
long m_lData;
string m_strData;
// ...... overload the other two types
};
立即发现问题了,首先GetData方法仅仅通过返回值无法重载,但立即想到我们能够略微修改下解决问题:
void GetData(long& data) {
data = m_lData;
}
void SetData(long data) {
m_lData = data;
}
void GetData(string& data) {
data = m_strData;
}
void SetData(const string& data) {
m_strData = data;
}
但细致看下还是有问题,没有满足需求2中的要求,即使用户使用的是整型数据,其它三种数据类型在结构中还是存在,内部数据有冗余。
那使用类模板不就能够解决问题吗?代码例如以下所看到的:
template<typename T>
class ExportData
{
public:
T GetData() {
return m_lData;
}
void SetData(T data) {
m_data = data;
}
private:
T m_data;
};
如此简单,这样就没有冗余了,可是这样却把全部仅仅要支持赋值操作的类型都支持了,不满足需求1。非常多人这时肯定会想到,这时使用下traits不就能解决问题了吗?(关于traits能够參考《活用C++模板之traits》)
template<typename T>
class ExportData
{
public:
RESULT GetData(T& data) {
return ERROR;
}
RESULT SetData(const T& data) {
return ERROR;
}
};
template<>
class ExportData<long>
{
public:
RESULT GetData(long& data) {
data = m_data;
return OK;
}
RESULT SetData(const long& data) {
m_data = data;
return OK;
}
private:
long m_data;
}; template<>
class ExportData<double>
...... // just like the implementation of long
template<>
class ExportData<string>
...... // just like the implementation of long
template<>
class ExportData<Binary>
...... // just like the implementation of long
满足需求1仅支持四种类型,满足需求2没有冗余,满足需求3统一的调用形式,可是对于需求4的问题,有点问题,由于当你使用int或者float时仍旧支持,也就是仅仅要数据间能够隐式转换,就不会返回错误提示调用方,那就再改善下吧:
template<typename T>
struct TypeTraits
{
static const DATA_TYPE field_type = TYPE_UNSUPPORTED;
};
template<>
struct TypeTraits<std::string>
{
static const DATA_TYPE field_type = TYPE_UTF8;
};
template<>
struct TypeTraits<long>
{
static const DATA_TYPE field_type = TYPE_INEGER;
};
template<>
struct TypeTraits<double>
{
static const DATA_TYPE field_type = TYPE_REAL;
};
template<>
struct TypeTraits<Binary>
{
static const DATA_TYPE field_type = TYPE_BINARY;
};

以上先通过Traits的方法获得一个能够用来推断是否是我们支持的数据类型的方式,成立则不支持,不成立则支持,推断方式例如以下:
TypeTraits<long>::field_type == TYPE_UNSUPPORTED

然后ExportData例如以下实现:
template<typename T>
class ExportData
{
public:
RESULT GetData(T& data) {
return ERROR;
}
RESULT SetData(const T& data) {
return ERROR;
}
};
template<>
class ExportData<long>
{
public:
RESULT GetData(long& data) {
if (TypeTraits<long>::field_type == TYPE_UNSUPPORTED) {
return ERROR;
}
data = m_data;
return OK;
}
RESULT SetData(const long& data) {
m_data = data;
return OK;
}
private:
long m_data;
};
如今仅仅有这四种类型会被支持,其它类型都会返回错误,似乎全部的需求都支持了,那这就是终于的解决方式吗?
不是!
我们忽略了ExportData在哪被使用了,它被用在了接口类的virtual方法中了,而因为C++编译的一些特性,C++语言本身是不支持虚函数本身又是模板函数的,也就是说下面接口类的定义编译绝对通只是(至于为什么C++不支持,这跟C++编译方式有关,这里就不解释了,假设有这样疑问的能够在回复中给我留言):
class IContainer
{
public:
template<typename T>
virtual RESULT Insert(const std::string& key, const ExportData<T>& data) = 0; virtual RESULT Delete(const std::string& key) = 0; template<typename T>
virtual RESULT Find(const std::string& key, ExportData<T>& data) = 0;
};

而IContainer本身不是仅仅跟某个类型相关的,也就是不可能把IContainer定义成模板类。怎么办呢?

因为virtual函数不能同一时候又是模板函数,所以ExportData类不能定义为模板类,那能尝试把ExportData类的Get和Set方法设置为模板方法来解决吗?这样做还是会存在一个问题,因为不能使模板类,那类成员变量的数据怎么去支持4种类型呢?答案是都处理成类型无关的二进制数据,也就是说保存数据的首地址以及数据大小,在Get和Set时依据当前类型通过memcpy进行拷贝来转换成指定类型。看下代码吧,TypeTraits的定义跟上面一样,这里就不反复了:
class ExportData
{
public:
ExportData() : _data(NULL), _size(0){}
ExportData(const ExportData& data) {
_data = NULL;
_size = 0;
AssignData(data._data, data._size);
_type = data._type;
}
~ExportData() {
if (_data) {
delete[] _data;
_data = NULL;
}
}
ExportData& operator=(const ExportData& data) {
this->AssignData(data._data, data._size);
this->_type = data._type;
return *this; template<typename T>
RESULT SetData(const T& data) {
if (TypeTraits<T>::field_type == TYPE_UNSUPPORTED) {
return ERROR;
}
AssignData((const char*)&data, sizeof(T));
_type = TypeTraits<T>::field_type;
return OK;
}
template<>
RESULT SetData<std::string>(const std::string& data) {
AssignData(data.c_str(), data.size());
_type = TYPE_UTF8;
return OK;
}
template<>
RESULT SetData<Binary>(const Binary& data) {
AssignData(data.GetBlobAddr(), data.GetSize());
_type = TYPE_BLOB;
return OK;
} template<typename T>
RESULT GetData(T& data) const {
if (TypeTraits<T>::field_type == TYPE_UNSUPPORTED ||
_data == NULL ||
TypeTraits<T>::field_type != _type) {
return ERROR;
}
memcpy(&data, _data, _size);
return OK;
}
template<>
RESULT GetData<std::string>(std::string& data) const {
if (TYPE_UTF8 != _type || _data == NULL) {
data = "";
return ERROR;
}
data.assign(_data, _size);
return OK;
}
template<>
RESULT GetData<Binary>(Binary& data) const {
if (TYPE_BLOB != _type || _data == NULL) {
data.SetBlobData(NULL, 0);
return ERROR;
}
data.SetBlobData(_data, _size);
return OK;
}
private:
void AssignData(const char* data, unsigned int size) {
if (_data) {
delete[] _data;
_data = NULL;
}
_size = size;
_data = new char[size];
memcpy(_data, data, _size);
}
char* _data;
unsigned long _size;
DATA_TYPE _type;
};
这就是当时的解决方式,能够算是满足了前面提出的4点需求,调用的时候代码例如以下:
ExportData data;
RESULT res = OK;
res = data.SetData<string>("DataTest");
assert(OK == res); string str;
res = data.GetData<string>(str);
assert(OK == res); res = data.SetData<long>(111);
long ldata = 0;
res = data.GetData<long>(ldata);
assert(OK == res);

我想肯定还有更好的解决方法,比方能够尝试在编译时就提示类型不支持而不是在执行时通过返回错误来提示。假设有更好的解决方式,欢迎一起讨论。

【C++模版之旅】项目中一次活用C++模板(traits)的经历的更多相关文章

  1. 【C++模版之旅】项目中一次活用C++模板(traits)的经历 -新注解

    问题与需求: 请读者先看这篇文章,[C++模版之旅]项目中一次活用C++模板(traits)的经历. 对于此篇文章提出的问题,我给出一个新的思路. talking is cheap,show me t ...

  2. 在使用vue+webpack模版创建的项目中使用font-awesome

    前言:最近使用vue+webpack进行一个小项目的开发,按照官方模版文档完成项目初始化后打算引入ont-awesome字体图标库进行使用,引入过程中遇到一些问题并解决,现记录如下. 一开始进展很顺利 ...

  3. 控制反转和spring在项目中可以带来的好处

    Spring实例化Bean的三种方式分别是: 1,xml配置使用bean的类构造器 <bean id="personService" class="cn.servi ...

  4. 采用EntLib5.0(Unity+Interception+Caching)实现项目中可用的Caching机制

    看了园子里很多介绍Caching的文章,多数都只介绍基本机制,对于Cache更新和依赖部分,更是只简单的实现ICacheItemRefreshAction接口,这在实际项目中是远远不够的.实际项目中, ...

  5. Android项目中如何用好构建神器Gradle?(转)

    最近在忙团队并行开发的事情,主要是将各个团队的代码分库,一方面可以降低耦合,为后面模块插件化做铺垫,另一方面采用二进制编译,可以加快编译速度.分库遇到了一些问题,很多都要通过Gradle脚本解决,所以 ...

  6. 如何在cocos2d项目中enable ARC

    如何在cocos2d项目中enable ARC 基本思想就是不支持ARC的代码用和支持ARC的分开,通过xcode中设置编译选项,让支持和不支持ARC的代码共存. cocos2d是ios app开发中 ...

  7. 关于如何正确地在android项目中添加第三方jar包

    在android项目中添加第三方jar包虽然不是一个很复杂的问题,但是确实给很多开发者带来了不小的困扰.我自己就曾经碰到过calss not found exception.error inflati ...

  8. 项目中如何使用babel6详解

    由于浏览器的版本和兼容性问题,很多es6,es7的新的方法都不能使用,等到可以使用的时候,可能已经过去了很多年.Babel可以把es6,es7的新代码编译成兼容绝大多数的主流浏览器的代码. 本篇文章主 ...

  9. 剑指Offer——企业级项目中分层的含义与依据及多态的优势

    剑指Offer--企业级项目中分层的含义与依据及多态的优势   关于以上两点,由于项目经验较少,自己不是很明白,特整理如下. 常见分层架构模式 三层架构 3-tier architecture   微 ...

随机推荐

  1. Mongoose即使是简单的表查询

    从我原来的博客尖,欢迎大家光临 http://www.hacke2.cn 像我这篇文章所说的基于Node.js + jade + Mongoose 模仿gokk.tv.当时停止开发是由于我深深的感觉到 ...

  2. CentOS 安装apache 及所需的 apr,apr-util,pcre

    安装apache前确定已安装 apr,apr-util,pcre 一.安装apr [root@xt test]# tar -zxf apr-1.4.5.tar.gz [root@xt test]# c ...

  3. 基于ICSharpCode.SharpZipLib.Zip的压缩解压缩

    原文:基于ICSharpCode.SharpZipLib.Zip的压缩解压缩 今天记压缩解压缩的使用,是基于开源项目ICSharpCode.SharpZipLib.Zip的使用. 一.压缩: /// ...

  4. sql function递归

    alter function Fn_GetUserGroupRelation ( @DHsItemID int ) returns nvarchar(1024) begin declare @Col_ ...

  5. WPF下的视频录制界面设计

    原文:WPF下的视频录制界面设计 在去年12月份,我曾经写过三篇文章讨论C#下视频录制.播放界面的设计.这三篇文章是:利用C#画视频录制及播放的界面(一) 利用C#画视频录制及播放的界面(二)利用C# ...

  6. python udp编程实例

    与python tcp编程控制见 http://blog.csdn.net/aspnet_lyc/article/details/39854569 c++ udp/tcp 编程见 http://blo ...

  7. 汉诺塔问题的java递归实现

    import java.util.Scanner; public class Hanoi { int count=0; public void hanoi(int n,char A,char B,ch ...

  8. java提高篇(七)-----详解内部类

    可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一个非常有用的特性但又比较难理解使用的特性(鄙人到现在都没有怎么使用过内部类,对内部类也只是略知一二). 第一次见面 内部类我们从外面 ...

  9. Spark里面:获取图Spark有多少行代码

    Spark1.0.0公布一个多月,有多少行代码就(Line of Code, LOC)? watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYW56aHNvZn ...

  10. 开销是有益的:AppCan 至HTML5移动创新和创业精神和健康

      2014年移动创业更趋向理性,消费级App市场接近饱和,BAT等巨头的竞争更加激烈,市场版图及格局基本定型.而企业级移动应用却迎来爆发增长,替代进入红海的消费级App市场,企业级定制APP开发成为 ...