概述:
traits是一种特性萃取技术,它在Generic Programming中被广泛运用,常常被用于使不同的类型可以用于相同的操作,或者针对不同类型提供不同的实现.traits在实现过程中往往需要用到以下三种C++的基本特性:
enum
typedef
template (partial) specialization
其中:
enum用于将在不同类型间变化的标示统一成一个,它在C++中常常被用于在类中替代define,你可以称enum为类中的define;
typedef则用于定义你的模板类支持特性的形式,你的模板类必须以某种形式支持某一特性,否则类型萃取器traits将无法正常工作.看到这里你可能会想,太苛刻了吧?其实不然,不支持某种特性本身也是一种支持的方式(见示例2,我们定义了两种标示,__xtrue_type和__xfalse_type,分别表示对某特性支持和不支持).
template (partial) specialization被用于提供针对特定类型的正确的或更合适的版本.
借助以上几种简单技术,我们可以利用traits提取类中定义的特性,并根据不同的特性提供不同的实现.你可以将从特性的定义到萃取,再到traits的实际使用统称为traits技术,但这种定义使得traits显得过于复杂,我更愿意将traits的定义限于特性萃取,因为这种定义使得traits显得更简单,更易于理解,^_^.
举例:
上面提到过,traits可被用于针对不同类型提供不同的实现,那么下面就举两个例子来说明如何实现这一点.
Example 1:
假定我们需要为某个类设计一个可以对所有类型(包括普通的int/long...,提供了clone方法的复杂类型CComplexObject,及由该类派生的类)进行操作的函数clone,下面,先用OO的方法来考虑一下解决方案.看到前面的条件,最先跳进你脑子里的肯定是Interface,pure virtual function等等.对于我们自己设计的类CComplexObject而言,这不是问题,但是,对于基本数据类型呢?还有那些没有提供clone方法的复杂类型呢?(这时候你可能会想,要是Java该多easy,所有类都默认从Object派生,而Object已提供了一个默认的clone方法,但是,要使类真正支持clone,还必须implements Cloneable,所以,同样也不能避免这里遇到的麻烦).
下面是一个可能的解决方案:
template <typename T, bool isClonable>
class XContainer
{
...
void clone(T* pObj)
{
if (isClonable)
{
pObj->clone();
}
else
{
//... non-Clonable algorithm ...
}
}
};
但是只要你测试一下,这段代码不能通过编译.为什么会这样呢?原因很简单:对于没有实现clone方法的非Clonable类或基本类型,pObj->clone这一句是非法的.
那么怎样解决上面的这个难题呢?上面不能通过编译的代码告诉我们,要使我们的代码通过编译,就不能使非Clonable类或基本类型的代码中出现pObj->clone,即我们需要针对不同类型提供不同的实现.为了实现这一点,我们可以在我们的模板类中用enum定义一个trait,以标示类是否为Clonable类,然后在原模板类内部引入一个traits提取类Traits,通过对该类进行specilizing,以根据不同的trait提供不同的实现.具体实现如下:
#include <iostream>
using namespace std;
class CComplexObject // a demo class
{
public:
void clone() { cout << "in clone" << endl; }
};
// Solving the problem of choosing method to call by inner traits class
template <typename T, bool isClonable>
class XContainer
{
public:
enum {Clonable = isClonable};
void clone(T* pObj)
{
Traits<isClonable>().clone(pObj);
}
template <bool flag>
class Traits
{
};
template <>
class Traits<true>
{
public:
void clone(T* pObj)
{
cout << "before cloning Clonable type" << endl;
pObj->clone();
cout << "after cloning Clonable type" << endl;
}
};
template <>
class Traits<false>
{
public:
void clone(T* pObj)
{
cout << "cloning non Clonable type" << endl;
}
};
};
void main()
{
int* p1 = 0;
CComplexObject* p2 = 0;
XContainer<int, false> n1;
XContainer<CComplexObject, true> n2;
n1.clone(p1);
n2.clone(p2);
}
编译运行一下,上面的程序输出如下的结果:
doing something non Clonable
before doing something Clonable
in clone
after doing something Clonable
这说明,我们成功地根据传入的isClonable模板参数为模板实例选择了不同的操作,在保证接口相同的情况下,为不同类型提供了不同的实现.
Example 2:
我们再对上面的例子进行一些限制,假设我们的clone操作只涉及基本类型和CComplexObject及其派生类,那么我们可以进一步给出下面的解法:
#include <iostream>
using namespace std;
struct __xtrue_type { }; // define two mark-type
struct __xfalse_type { };
class CComplexObject // a demo class
{
public:
virtual void clone() { cout << "in clone" << endl; }
};
class CDerivedComplexObject : public CComplexObject // a demo derived class
{
public:
virtual void clone() { cout << "in derived clone" << endl; }
};
// A general edtion of Traits
template <typename T>
struct Traits
{
typedef __xfalse_type has_clone_method; // trait 1: has clone method or not? All types defaultly has no clone method.
};
// Specialized edtion for ComplexObject
template <>
struct Traits<CComplexObject>
{
typedef __xtrue_type has_clone_method;
};
template <typename T>
class XContainer
{
template <typename flag>
class Impl
{
};
template <>
class Impl <__xtrue_type>
{
public:
void clone(T* pObj)
{
pObj->clone();
}
};
template <>
class Impl <__xfalse_type>
{
public:
void clone(T* pObj)
{
}
};
public:
void clone(T* pObj)
{
Impl<Traits<T>::has_clone_method>().clone(pObj);
}
};
void main()
{
int* p1 = 0;
CComplexObject c2;
CComplexObject* p2 = &c2;
CDerivedComplexObject c3;
CComplexObject* p3 = &c3; // you must point to a derived object by a base-class pointer,
//it's a little problem
XContainer<int> n1;
XContainer<CComplexObject> n2;
XContainer<CComplexObject> n3;
n1.clone(p1);
n2.clone(p2);
n3.clone(p3);
}
现在,所有基本类型以及CComplexObject类系都可以用于XContainer了.
结语:
看到这里,你或许会说,traits不过如此,还以为是什么高深的玩意呢!其实技术就是这样,说白了都很Easy,关键是怎么将他们用于实际,为实际的Designing/Development服务.毕竟,在IT领域,不能应用于实际的技术是没有价值的.
C++ traits学习笔记(二)
这是我学习traits翻译的第二篇文章,原文地址:http://accu.org/index.php/journals/442,有不当的地方请过路的朋友指教,谢谢!
翻译文章也怪不容易的,转载请注明出处,感谢!
不同的代码片段具有相同的结构,仅在实现细节方面有所不同,这是一种常见的情形。因此我们可以重用相同的部分,只对于有差别的那些细节,针对不同的需求进行实现。在C语言中,可以通过函数指针实现这一目标。比如C标准库中的qsort函数中那个函数指针(这句话在我看来真是相当风骚。。。以前只是无脑去用qsort,从来没有想过设计方面的事,不管你湿没湿,反正我湿了。。。读了linux的协议栈的部分代码之后,对这一点体会又有所加深。)或是C++里的虚函数。但这些方法增加了运行时的开销(我想这里应该是指函数指针和虚函数这两种方法,只有在运行时才能确定到底调用哪个函数)。
C++通过模板引入了泛型编程,搞定了运行时绑定这个问题,但是乍一看泛型这玩意似乎仍是无奈之举。毕竟同一个算法不可能在所有的数据结构上都有好的表现:比如,链表和数组排序方式是不同的,有序数据的查找也要快于无序数据。
于是traits来了。
Bjarne Stroustrup大神说:Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine "policy" or "implementation details".
trait是一类小对象,其目的是包含一些实现细节方面的信息,供其他对象和算法使用。
C和C++程序员一般会比较熟悉limits.h和float.h,这两个头文件决定了整型和浮点型的各种属性。C++程序员还会比较熟悉std::numeric_limits,乍一看这个类只是以不同的实现方式提供了与前两个头文件相同的功能,进一步解读这个类,可以发现traits的第一个优势:一致的接口。
如果使用limits.h和float.h,程序员必须记住类型的前缀(prefix)和特性(trait)。比如,DBL_MAX包含了double类型的最大值特性(我的理解是DBL即prefix,MAX就是trait)。但如果使用类似numeric_limits这样的traits类,就可以用numeric_limits<double>::max()来表示double类型的最大值。更为重要的是,甚至可以不必关心所要使用的类型。比如下面这个返回数组最大元素的简单模板函数:
1: template< class T >
2: T findMax(const T const * data, const size_t const numItems) {
3: // Obtain the minimum value for type T
4: T largest = std::numeric_limits< T >::min();
5: for(unsigned int i=0; i<numItems; ++i)
6: if (data[i] > largest)
7: largest = data[i];
8: return largest;
9: }
使用了numeric_limits之后,只要创建相应的特化模板,就可以把上面的模板扩展到任意的自定义类型。
下面说说如何创建自己需要的traits类。以boost的is_void trait为例。
首先定义一个实现默认行为的泛型模板。由于当具有类型时,都不是void,所以此时is_void::value应该为false,所以有:
1: template< typename T >
2: struct is_void{
3: static const bool value = false;
4: };
然后再对这个模板进行扩充,加入对void的特化。
1: template<>
2: struct is_void< void >{
3: static const bool value = true;
4: };
这样我们就有了一个完整的traits类型。用它可以判断任意一个作为模板参数传入的类型是否为void。
这回以boost::is_pointer为例。和刚才一样,先定义一个默认的模板。
1: template< typename T >
2: struct is_pointer{
3: static const bool value = false;
4: };
再对所有指针类型加入一个具体化的模板:
1: template< typename T >
2: struct is_pointer< T* >{
3: static const bool value = true;
4: };
接下来要考虑篇首提到的问题:如何用traits技术在编译时选择合适的算法?
通过下面的例子来说明。在下面的例子中,根据算法所操作的对象,在编译时选择是使用标准算法(即对象的类型不支持优化算法)还是优化算法(对象的类型支持优化算法)。
还是先创建一个默认的traits类,将其命名为supports_optimised_implementation,除了名字,这个traits类与is_void完全一样。即:
1: template<typename T>
2: struct supports_optimised_implementation{
3: static const bool value = false;
4: };
接下来在模板algorithm_selector中实现默认算法。在本例中,由于只在标准算法和优化算法之间进行选择,所以algorithm_selector使用bool类型进行参数化。如果要在更多算法之间进行选择的话,可以把模板参数类型换成int或者enum。这里当参数值为true时,表示使用优化算法。
1: template< bool b >
2: struct algorithm_selector {
3: template< typename T >
4: static void implementation( T& object )
5: {
6: //implement the alorithm operating on "object" here
7: }
8: };
1: template<>
2: struct algorithm_selector< true > {
3: template< typename T >
4: static void implementation( T& object ) {
5: object.optimised_implementation();
6: }
7: };
接下来给出供算法最终用户调用的泛型函数。注意,该函数调用algorithm_selector,而algorithm_selector用我们定义的supports_optimised_implementation traits类进行参数化。
1: template< typename T >
2: void algorithm( T& object ) {
3: algorithm_selector< supports_optimised_implementation< T >::value >::implementation(object);
4: }
假设现在有两个类ObjectA和ObjectB,A不支持优化算法,B支持。则有:
1: class ObjectB {
2: public:
3: void optimised_implementation() {
4: //...
5: }
6: };
还需要对supports_optimised_implementation加上针对B类的特化:
1: template<>
2: struct supports_optimised_implementation< ObjectB > {
3: static const bool value = true;
4: };
最后,当对模板进行实例化时:
1: int main(int argc, char* argv[]) {
2: ObjectA a;
3: algorithm( a );
4: // calls default implementation
5: ObjectB b;
6: algorithm( b );
7: // calls
8: // ObjectB::optimised_implementation();
9: return 0;
10: }
Over。
整理一下思路。实际上,用户调用的函数是algorithm,这个函数的用户唯一关心的是算法的功能。比如排序,用户希望,不管要排序的对象是什么类型,只要调用排序算法,就可以无差别实现各类对象的排序。因此,在调用算法时,肯定不希望函数的形式是类似algorithm(object, algorithm_type)这种,即用户自己还需要了解该对象适合使用哪种算法,如果是这样,用户还需要去了解对象的细节。因此在上面的例子中,在algorithm的内部进行了算法的选择。
相关推荐
3.4. Traits:创建交互对话
Traits 项目是所有 Enthought 工具套件开发的中心,它改变了 Enthought 使用已经非常高效的 Python 编程语言进行编程的思维模型。 我们鼓励每个人与我们一起享受使用如此强大的方法所带来的生产力收益。 Traits ...
在Rust编程语言中,`num-traits`库是一个非常重要的工具,它为各种数值类型提供了通用的数学操作和特性接口。这个库的目标是增强Rust的类型系统,使其能够支持泛型编程中的数学运算,使得开发者能够在不指定具体类型...
特质 go-traits是一个概念包,可使用嵌入式结构和挂钩接口帮助实现行为。 特性列表: traits.Hasher唯一哈希生成器的扩展。... traits.schedule实现traits.schedule以在traits.Init调用的单独goroutine
移入traits目录 cd traits 制作sqlite3数据库配置文件和本地环境文件 cd config cp database.yml.sqlite database.yml cp local_env.yml.example local_env.yml 返回基本目录并安装gems cd .. bundle ...
在Rust编程语言中,`stm32-rcc-traits`库提供了对STM32 RCC寄存器进行安全、类型安全访问的接口。这个库使得开发者能够在编写固件时更方便地管理和控制STM32的时钟系统,提高代码的可读性和可靠性。 首先,我们需要...
"laravel-controller-repository-traits"是一个专门为Laravel设计的组件,它引入了Repository模式和Traits的概念,旨在提高代码的可复用性和组织性。下面将详细探讨这个主题。 首先,我们来看“Controller”和...
已弃用不建议再使用该项目。...在 trait 的代码中,您可以访问字段,这些字段未在 trait 中声明,但会在后代类声明安装: haxelib install traits 基本示例: interface TWorker extends traits. I
### C++ Traits:类型属性封装与偏特化 在C++中,`Traits`类是一种非常重要的设计模式,它主要用于封装类型的各种属性。通过这种方式,我们可以更方便地处理不同类型的数据结构,实现代码的复用,并优化性能。本文...
<br>Traits:类型的else-if-then机制 | Traits: the else-if-then of Types Traits on Steroids中文版 | Traits on Steroids 类型和数值间的映射 | Mappings between Types and Values 简化异常...
【标题】"Tejon_Functional_Traits:在第戎牧场封闭试验中发现草食动物密度与气候之间关系的项目" 该项目“Tejon_Functional_Traits”聚焦于探究草食动物密度与气候之间的相互作用,特别是在第戎牧场这样一个特定的...
复杂特征 :red_exclamation_mark: :anguished_face: :red_exclamation_mark: :anguished_face: :red_exclamation_mark: :anguished_face: :red_exclamation_mark: :anguished_face: :red_exclamation_mark: :...
Laravel雄辩的特质 Laravel雄辩的特质您可以将这些特征集添加到Laravel Eloquent模型中,以...安装作曲家Laravel Eloquent Traits可以通过作曲家安装: composer require two-thirds/eloquent-traits可用特征地理位置
"spatial_sp_traits"项目,根据标题和描述,似乎是一个基于R语言的项目,它专门用于处理和分析空间数据,可能与美国国家科学基金会(NSF)资助的UCR(University of California, Riverside)项目有关。这个项目可能...
Boost.CallableTraits CallableTraits是一个C ++ 11仅限标头的库,用于检查,合成和分解可调用类型。 最新文档可。 CallableTraits已被并被。 CallableTraits在Boost 1.66(2017年12月)及更高版本中可用。...
SeedClim_Traits项目是一个基于R语言的数据集和分析工具,专注于研究种子植物在不同气候条件下的功能性状。这个项目的核心在于将气候数据与植物物种的生物学特性相结合,以理解气候变化对全球种子植物群落的影响。...
复杂性状根据费舍尔的无穷小模型,显示数量性状的多基因起源的闪亮应用程序。 要运行应用程序,请输入 R library(shiny)runGitHub("complex.traits", "santiagombv")
模型特征库这是一个简单的库,用于非常灵活地将模拟信息应用于模型。仿真信息可能包含边界条件,但也可以引用在模型设置期间可能需要查询的其他数据。例如,数据写入文件的频率或材料属性。模型特征存储在具有两种...
在Laravel框架中,Model Traits是一种强大的代码复用机制,它们允许你在多个模型类之间共享特定的行为或属性。本项目“Laravel开发-laravel-model-traits”聚焦于为Laravel 5模型提供一系列预定义的Traits,以简化...
Symfony特质 很少有辅助功能可以在Symfony中更快地开发API 形式特质 handleJSONForm POST , PUT或PATCH请求内容中发送的JSON数据的控制器辅助方法 一般来说,对于带有JS框架的CRUD,发送JSON内容比表单更灵活。...