您在哪里发现模板有用

Where do you find templates useful?

本文关键字:有用 发现 在哪里      更新时间:2023-10-16

在我的工作场所,我们倾向于使用iostream,string,vector,map和一两个奇数算法 我们实际上还没有发现很多模板技术是问题的最佳解决方案的情况。

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

作为贿赂,期待你的答案投赞成票。

有关模板的一般信息:

每当您需要使用相同的代码但对不同的数据类型进行操作时,模板都很有用,其中类型在编译时是已知的。 以及当您有任何类型的容器对象时。

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

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

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

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

现在进入很酷的东西:

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

看看这个来自维基百科的模板元编程的例子。 它演示如何使用模板在编译时执行代码。 因此,在运行时,您有一个预先计算的常量。

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
}

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

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

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

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

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

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

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

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 ;

我使用模板(在 Boost.Fusion 的帮助下)为我正在开发的超图库实现类型安全的整数。 我有一个(超)边 ID 和一个顶点 ID,两者都是整数。 使用模板时,顶点 ID 和超边缘 ID 成为不同的类型,并且在预期使用另一个时使用它们会生成编译时错误。 为我节省了很多运行时调试所困扰的麻烦。

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

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);

然后是具有"默认"值的变体。如果存在,则返回键的值,如果不存在,则返回默认值。模板使我不必自己创建 6 个新功能。

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

我经常使用的模板是大量的容器类,提升智能指针,范围保护,一些STL算法。

我编写模板的场景:

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

    bool 包含楠(float * , int)bool 包含楠(双 *, 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)

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


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

我们使用 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;
}

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

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

现在,这个功能有什么好看的。假设您正在编写一个供其他人使用的接口。您知道将使用您的接口,因为它是其自身域中的模块。但是你还不知道人们将如何使用它。基于策略的设计增强了您的代码以供将来重用;它使您独立于特定实现所依赖的数据类型。代码只是"啜饮"。:-)

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

我曾经看到过以下代码:

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 函数指针或使用宏要好。

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

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<>派生类型的泛型多态函数:

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));
};

前面已经提到,您可以使用模板作为策略类来执行某些操作。我经常使用它。

我还在属性映射的帮助下使用它们(有关此的更多信息,请参阅boost站点),以便以通用方式访问数据。这提供了更改存储数据方式的机会,而无需更改检索数据的方式。