【C++模版之旅】项目中一次活用C++模板(traits)的经历

曾经曾在一个项目中碰到过一个挺简单的问题,但一时又不能用普通常规的方法去非常好的解决,最后通过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);

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

时间: 2024-10-16 14:58:57

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

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

问题与需求: 请读者先看这篇文章,[C++模版之旅]项目中一次活用C++模板(traits)的经历. 对于此篇文章提出的问题,我给出一个新的思路. talking is cheap,show me the code. 代码: class ExportData { union { string * sp; long* lp; double* dp; void* vp; }; enum my_type {SP,LP,DP} types; static unordered_map<type_index

项目中如何使用babel6详解

由于浏览器的版本和兼容性问题,很多es6,es7的新的方法都不能使用,等到可以使用的时候,可能已经过去了很多年.Babel可以把es6,es7的新代码编译成兼容绝大多数的主流浏览器的代码. 本篇文章主要介绍在项目中如何安装配置和使用babel. 1.在项目下初始化 package.json $ npm init 2.在项目中安装babel $ npm install babel-cli --save-dev 3.安装babel插件 $ npm install babel-preset-xxxxx

导入开源库到基于Android Studio构建的项目中

前两天,谷歌发布了Android Studio 1.0的正式版,也有更多的人开始迁移到Android Studio进行开发.然而,网上很多的开源库,控件等还是以前的基于Eclipse进行开发,很多人不知道怎么导入到自己的基于Android Studio项目中来,微博上也有人私信我,让我来写写,正好今天回来的比较早,就写写吧.主要介绍一下常见的一些导包的场景. 前言 --project //项目目录 | build.gradle //项目的gradle配置文件 | settings.gradle

关于项目中的DAL数据接入层架构设计

摘要:项目中对关系型数据库的接入再寻常不过,也有海量的ORM工具可供选择,一个一般性的DAL数据接入层的结构却大同小异,这里就分享一下使用Hibernate.Spring.Hessian这三大工具对DAL层的具体实现方法,也是对之前使用的一个总结. 关键词:Hibernate, Spring, Hessian, DAL, 数据接入层, 架构设计 注意:以下配置或代码运行在Hibernate4.2.5,Spring3.2.4,Hessian4.0.37,Tomcat7.0.47环境下 一.Mode

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

1.下载第三方jar包 2.在android项目下创建一个libs目录(名称并不固定,你完全可以取其他名称) 3.在eclipse中右键点击libs目录,依次选择Import -> General -> File System,选中jar包所在目录, 然后选中这个目录下的jar包 注:到这一步为止你就成功地把jar包添加到项目中(但是还没有被android的虚拟机识别,因此如果这时你使用jar包中的类,编译都无法通过) 4.右键点击项目名,依次选择Build Path -> Config

我们为什么要把Dagger2,MVP以及Rxjava引入项目中?

1Why? 毫无疑问在Android开发圈中这三个技术是经常被提及的,如此多的文章和开源项目在介绍他们,使用他们,开发者也或多或少的被带动起来在自己的项目中使用他们,但是使用他们之前我们知道为什么要使用他们,他们能给我们带来什么好处吗,还是只是跟随潮流 其实我们大多数项目中是使用不到他们的,或者说对这些技术的需求不是很大,为什么这么说呢? 大多数的开发者其实都是在开发功能模块比较少的小项目,对于这些项目来说,其实使用这些技术带来的好处相对于在开发时的所付出的时间来说其实性价比并不高,因为学习这些

C# 获取文件路径,读取项目中某程序集下文件

获取文件路径 ------------------------------------------------------------------------- winform获取文件路径: string str1 =Process.GetCurrentProcess().MainModule.FileName;//获得当前执行的exe的文件名.string str2=Environment.CurrentDirectory;//获取和设置当前目录的完全限定路径.string str3=Dire

NVelocity模板引擎在项目中的应用

NVelocity是一个基于.NET的模板引擎(template engine).它允许任何人仅仅简单的使用模板语言(template language)来引用由.NET代码定义的对象.至于其他的一些别的NVelocity的的解释.和一些用法这里就不在提及. 具体的使用方法: 1,定义Velocity辅助类(VelocityHelper.cs) 提供下载  当然,这个辅助类需要添加NVelocity.dll的引用. 2,aspx页面和Template的结构说明 这个其实是需要根据个人喜好来定!为

node开发指南中的microblog项目中遇到的问题总结及解决方法

1      使用connect-mongdo时,报错:Cannot read property 'Store' of undefined 解决: require('connect-mongo')的时候加一个参数express,如下: var express = require('express'); var MongoStore = require('connect-mongo')(express), 2      使用app.use(express.router(routers)) 提示 h