你在哪里找到有用的模板?

时间:2008-10-26 00:49:34

标签: c++ templates metaprogramming generic-programming

在我的工作场所,我们倾向于使用 iostream 字符串矢量地图,以及奇数算法或两个。我们实际上并没有发现很多情况,模板技术是问题的最佳解决方案。

我在这里寻找的是想法,以及可选的示例代码,它们展示了您如何使用模板技术为您在现实生活中遇到的问题创建新的解决方案。

作为贿赂,期待对你的回答进行投票。

12 个答案:

答案 0 :(得分:11)

模板的一般信息:

模板在您需要使用相同代码但在不同数据类型上运行时非常有用,其中类型在编译时是已知的。当你有任何类型的容器对象时。

一种非常常见的用法是几乎所有类型的数据结构。例如:单链表,双链表,树,尝试,哈希表,......

另一个非常常见的用法是排序算法。

使用模板的一个主要优点是您可以删除代码重复。代码重复是编程时应该避免的最重要的事情之一。

您可以将函数Max实现为宏或模板,但模板实现将是类型安全的,因此更好。

现在谈论很酷的事情:

另请参阅template metaprogramming,这是一种在编译时而不是在运行时预先评估代码的方法。模板元编程只有不可变变量,因此其变量不能改变。由于这种模板,元编程可以看作是一种函数式编程。

查看维基百科中的模板元编程示例。它显示了模板如何用于在编译时执行代码。因此,在运行时,您有一个预先计算的常量。

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
    int x = Factorial<4>::value; // == 24
    int y = Factorial<0>::value; // == 1
}

答案 1 :(得分:8)

我使用了很多模板代码,主要是在Boost和STL中,但我很少需要任何。

几年前的一个例外是在一个操作Windows PE格式EXE文件的程序中。该公司希望添加64位支持,但我为处理文件而编写的ExeFile类仅适用于32位版本。操作64位版本所需的代码基本相同,但它需要使用不同的地址类型(64位而不是32位),这导致其他两个数据结构也不同。

基于STL使用单个模板来支持std::stringstd::wstring,我决定尝试将ExeFile作为模板,使用不同的数据结构和地址类型参数。有两个地方我仍然需要使用#ifdef WIN64行(略有不同的处理要求),但这并不是很难。我们现在已经在该程序中获得了完整的32位和64位支持,并且使用该模板意味着我们所做的每一次修改都会自动应用于这两个版本。

答案 2 :(得分:7)

我使用模板创建自己的代码的一个地方是实现策略类,如Andrei Alexandrescu在Modern C ++ Design中所描述的。目前我正在开发一个项目,其中包括一组与BEA \ h \ h \ h Oracle的Tuxedo TP监视器交互的类。

Tuxedo提供的一个工具是事务持久性队列,所以我有一个与队列交互的TpQueue类:

class TpQueue {
public:
   void enqueue(...)
   void dequeue(...)
   ...
}

然而,由于队列是事务性的,我需要决定我想要的事务行为;这可以单独在TpQueue类之外完成,但我认为如果每个TpQueue实例在事务上都有自己的策略,那么它更明确且更不容易出错。所以我有一组TransactionPolicy类,例如:

class OwnTransaction {
public:
   begin(...)  // Suspend any open transaction and start a new one
   commit(..)  // Commit my transaction and resume any suspended one
   abort(...)
}

class SharedTransaction {
public:
   begin(...)  // Join the currently active transaction or start a new one if there isn't one
   ...
}

TpQueue类被重写为

template <typename TXNPOLICY = SharedTransaction>
class TpQueue : public TXNPOLICY {
   ...
}

所以在TpQueue中我可以根据需要调用begin(),abort(),commit(),但可以根据我声明实例的方式改变行为:

TpQueue<SharedTransaction> queue1 ;
TpQueue<OwnTransaction> queue2 ;

答案 3 :(得分:4)

我使用模板(在Boost.Fusion的帮助下)来实现我正在开发的超图库的类型安全整数。我有一个(超)边缘ID和一个顶点ID,它们都是整数。使用模板,vertex和hyperedge ID成为不同的类型,当另一个预期时使用一个ID会产生编译时错误。为我节省了很多头疼,否则我会对运行时调试感到头疼。

答案 4 :(得分:3)

这是一个真实项目的例子。我有这样的getter函数:

bool getValue(wxString key, wxString& value);
bool getValue(wxString key, int& value);
bool getValue(wxString key, double& value);
bool getValue(wxString key, bool& value);
bool getValue(wxString key, StorageGranularity& value);
bool getValue(wxString key, std::vector<wxString>& value);

然后是具有'default'值的变体。如果key存在则返回key的值,如果不存在则返回默认值。模板使我不必自己创建6个新函数。

template <typename T>
T get(wxString key, const T& defaultValue)
{
    T temp;
    if (getValue(key, temp))
        return temp;
    else
        return defaultValue;
}

答案 5 :(得分:2)

我经常使用的模板是众多容器类,提升智能指针,scopeguards,一些STL算法。

我编写模板的场景:

  • 自定义容器
  • 内存管理,在void * allocators之上实现类型安全和CTor / DTor调用
  • 具有不同类型的重载的常见实现,例如

    bool ContainsNan(float *,int) bool ContainsNan(double *,int)

它们都只调用(本地隐藏)辅助函数

template <typename T>
bool ContainsNanT<T>(T * values, int len) { ... actual code goes here } ;

与类型无关的特定算法,只要该类型具有某些属性,例如二进制序列化。

template <typename T>
void BinStream::Serialize(T & value) { ... }

// to make a type serializable, you need to implement
void SerializeElement(BinStream & strean, Foo & element);
void DeserializeElement(BinStream & stream, Foo & element)

与虚拟功能不同,模板允许进行更多优化。


通常,模板允许为多种类型实现一个概念或算法,并且在编译时已经解决了差异。

答案 6 :(得分:2)

我们使用COM并接受指向一个对象的指针,该对象可以直接实现另一个接口,也可以通过[IServiceProvider](http://msdn.microsoft.com/en-us/library/cc678965(VS.85).aspx)这促使我创建这个类似助手类型的函数。

// Get interface either via QueryInterface of via QueryService
template <class IFace>
CComPtr<IFace> GetIFace(IUnknown* unk)
{
    CComQIPtr<IFace> ret = unk; // Try QueryInterface
    if (ret == NULL) { // Fallback to QueryService
        if(CComQIPtr<IServiceProvider> ser = unk)
            ser->QueryService(__uuidof(IFace), __uuidof(IFace), (void**)&ret);
    }
    return ret;
}

答案 7 :(得分:1)

我使用模板来指定函数对象类型。我经常编写将函数对象作为参数的代码 - 集成函数,优化函数等等 - 我发现模板比继承更方便。所以我的代码接收一个函数对象 - 比如积分器或优化器 - 有一个模板参数来指定它操作的函数对象的类型。

答案 8 :(得分:1)

显而易见的原因(比如通过操作不同的数据类型来防止代码重复)除此之外,还有一种非常酷的模式,称为基于策略的设计。我问了一个关于policies vs strategies的问题。

现在,这个功能真是太棒了。考虑您正在编写一个供其他人使用的界面。您知道将使用您的界面,因为它是自己域中的模块。但你还不知道人们将如何使用它。基于策略的设计可以增强您的代码,以便将来重用;它使您独立于特定实现所依赖的数据类型。代码只是“悄悄进入”。 : - )

特质本身就是个好主意。他们可以将特定的行为,数据和类型数据附加到模型。特征允许完成所有这三个字段的参数化。最好的,这是使代码可重用的一种非常好的方法。

答案 9 :(得分:1)

我曾经看过以下代码:

void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
   // three lines of code
   callFunctionGeneric1(c) ;
   // three lines of code
}

重复十次:

void doSomethingGeneric2(SomeClass * c, SomeClass & d)
void doSomethingGeneric3(SomeClass * c, SomeClass & d)
void doSomethingGeneric4(SomeClass * c, SomeClass & d)
// Etc

每个函数都有相同的6行代码复制/粘贴,每次调用另一个函数callFunctionGenericX,后缀相同。

没有办法完全重构整个事情。所以我保留了本地的重构。

我这样改变了代码(从内存中):

template<typename T>
void doSomethingGenericAnything(SomeClass * c, SomeClass & d, T t)
{
   // three lines of code
   t(c) ;
   // three lines of code
}

用以下内容修改现有代码:

void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
   doSomethingGenericAnything(c, d, callFunctionGeneric1) ;
}

void doSomethingGeneric2(SomeClass * c, SomeClass & d)
{
   doSomethingGenericAnything(c, d, callFunctionGeneric2) ;
}

这有点劫持模板的事情,但最后,我想它比使用typedefed函数指针或使用宏更好。

答案 10 :(得分:1)

已经提到过,你可以使用模板作为策略类来做某事。我经常使用它。

我还在属性映射(see boost site for more information on this)的帮助下使用它们,以便以通用方式访问数据。这样就有机会改变存储数据的方式,而无需改变检索数据的方式。

答案 11 :(得分:1)

我个人使用了奇怪的重复模板模式作为强制执行某种形式的自上而下设计和自下而上实现的方法。一个示例是通用处理程序的规范,其中在编译时对派生类型强制执行表单和接口上的某些要求。它看起来像这样:

template <class Derived>
struct handler_base : Derived {
  void pre_call() {
    // do any universal pre_call handling here
    static_cast<Derived *>(this)->pre_call();
  };

  void post_call(typename Derived::result_type & result) {
    static_cast<Derived *>(this)->post_call(result);
    // do any universal post_call handling here
  };

  typename Derived::result_type
  operator() (typename Derived::arg_pack const & args) {
    pre_call();
    typename Derived::result_type temp = static_cast<Derived *>(this)->eval(args);
    post_call(temp);
    return temp;
  };

};

可以使用这样的东西来确保您的处理程序派生自此模板并强制执行自上而下的设计,然后允许自下而上的自定义:

struct my_handler : handler_base<my_handler> {
  typedef int result_type; // required to compile
  typedef tuple<int, int> arg_pack; // required to compile
  void pre_call(); // required to compile
  void post_call(int &); // required to compile
  int eval(arg_pack const &); // required to compile
};

然后,这允许您具有仅处理handler_base&lt;&gt;的通用多态函数。派生类型:

template <class T, class Arg0, class Arg1>
typename T::result_type
invoke(handler_base<T> & handler, Arg0 const & arg0, Arg1 const & arg1) {
  return handler(make_tuple(arg0, arg1));
};