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

发布于 2024-07-08 02:25:28 字数 265 浏览 16 评论 0原文

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

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

作为贿赂,期待您的答案得到赞成票。

At my workplace, we tend to use iostream, string, vector, map, and the odd algorithm or two. We haven't actually found many situations where template techniques were a best solution to a problem.

What I am looking for here are ideas, and optionally sample code that shows how you used a template technique to create a new solution to a problem that you encountered in real life.

As a bribe, expect an up vote for your answer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

失而复得 2024-07-15 02:25:29

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

现在,我们来看看这个功能有什么妙处。 考虑一下您正在编写一个供其他人使用的界面。 您知道您的接口将被使用,因为它是其自己域中的模块。 但您还不知道人们将如何使用它。 基于策略的设计可增强您的代码以供将来重用; 它使您独立于特定实现所依赖的数据类型。 代码只是“吞入”。 :-)

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

The obvious reasons (like preventing code-duplication by operating on different data types) aside, there is this really cool pattern that's called policy based design. I have asked a question about policies vs strategies.

Now, what's so nifty about this feature. Consider you are writing an interface for others to use. You know that your interface will be used, because it is a module in its own domain. But you don't know yet how people are going to use it. Policy-based design strengthens your code for future reuse; it makes you independent of data types a particular implementation relies on. The code is just "slurped in". :-)

Traits are per se a wonderful idea. They can attach particular behaviour, data and typedata to a model. Traits allow complete parameterization of all of these three fields. And the best of it, it's a very good way to make code reusable.

叹沉浮 2024-07-15 02:25:29

我曾经看到下面的代码:

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

I once saw the following code:

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

repeated ten times:

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

Each function having the same 6 lines of code copy/pasted, and each time calling another function callFunctionGenericX with the same number suffix.

There were no way to refactor the whole thing altogether. So I kept the refactoring local.

I changed the code this way (from memory):

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

And modified the existing code with:

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

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

Etc.

This is somewhat highjacking the template thing, but in the end, I guess it's better than play with typedefed function pointers or using macros.

忆沫 2024-07-15 02:25:29

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

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

I personally have used the Curiously Recurring Template Pattern as a means of enforcing some form of top-down design and bottom-up implementation. An example would be a specification for a generic handler where certain requirements on both form and interface are enforced on derived types at compile time. It looks something like this:

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

};

Something like this can be used then to make sure your handlers derive from this template and enforce top-down design and then allow for bottom-up customization:

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

This then allows you to have generic polymorphic functions that deal with only handler_base<> derived types:

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));
};
铜锣湾横着走 2024-07-15 02:25:29

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

我也在属性映射的帮助下使用它们(参见 boost网站了解更多信息),以便以通用方式访问数据。 这使您有机会更改存储数据的方式,而无需更改检索数据的方式。

It's already been mentioned that you can use templates as policy classes to do something. I use this a lot.

I also use them, with the help of property maps (see boost site for more information on this), in order to access data in a generic way. This gives the opportunity to change the way you store data, without ever having to change the way you retrieve it.

血之狂魔 2024-07-15 02:25:28

有关模板的一般信息:

只要您需要使用相同的代码但对不同的数据类型进行操作(这些类型在编译时已知),模板就非常有用。 当你有任何类型的容器对象时也是如此。

一个非常常见的用法适用于几乎所有类型的数据结构。 例如:单链表、双向链表、树、尝试、散列表……

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

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

您可以将函数 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
}

General info on templates:

Templates are useful anytime you need to use the same code but operating on different data types, where the types are known at compile time. And also when you have any kind of container object.

A very common usage is for just about every type of data structure. For example: Singly linked lists, doubly linked lists, trees, tries, hashtables, ...

Another very common usage is for sorting algorithms.

One of the main advantages of using templates is that you can remove code duplication. Code duplication is one of the biggest things you should avoid when programming.

You could implement a function Max as both a macro or a template, but the template implementation would be type safe and therefore better.

And now onto the cool stuff:

Also see template metaprogramming, which is a way of pre-evaluating code at compile-time rather than at run-time. Template metaprogramming has only immutable variables, and therefore its variables cannot change. Because of this template metaprogramming can be seen as a type of functional programming.

Check out this example of template metaprogramming from Wikipedia. It shows how templates can be used to execute code at compile time. Therefore at runtime you have a pre-calculated constant.

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
}
枕头说它不想醒 2024-07-15 02:25:28

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

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

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

I've used a lot of template code, mostly in Boost and the STL, but I've seldom had a need to write any.

One of the exceptions, a few years ago, was in a program that manipulated Windows PE-format EXE files. The company wanted to add 64-bit support, but the ExeFile class that I'd written to handle the files only worked with 32-bit ones. The code required to manipulate the 64-bit version was essentially identical, but it needed to use a different address type (64-bit instead of 32-bit), which caused two other data structures to be different as well.

Based on the STL's use of a single template to support both std::string and std::wstring, I decided to try making ExeFile a template, with the differing data structures and the address type as parameters. There were two places where I still had to use #ifdef WIN64 lines (slightly different processing requirements), but it wasn't really difficult to do. We've got full 32- and 64-bit support in that program now, and using the template means that every modification we've done since automatically applies to both versions.

夜清冷一曲。 2024-07-15 02:25:28

我使用模板创建自己的代码的一个地方是实现策略类,如 Andrei Alexandrescu 在《现代 C++ 设计》中所描述的那样。 目前我正在开发一个项目,其中包含一组与 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 ;

One place that I do use templates to create my own code is to implement policy classes as described by Andrei Alexandrescu in Modern C++ Design. At present I'm working on a project that includes a set of classes that interact with BEA\h\h\h Oracle's Tuxedo TP monitor.

One facility that Tuxedo provides is transactional persistant queues, so I have a class TpQueue that interacts with the queue:

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

However as the queue is transactional I need to decide what transaction behaviour I want; this could be done seperately outside of the TpQueue class but I think it's more explicit and less error prone if each TpQueue instance has its own policy on transactions. So I have a set of TransactionPolicy classes such as:

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
   ...
}

And the TpQueue class gets re-written as

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

So inside TpQueue I can call begin(), abort(), commit() as needed but can change the behaviour based on the way I declare the instance:

TpQueue<SharedTransaction> queue1 ;
TpQueue<OwnTransaction> queue2 ;
り繁华旳梦境 2024-07-15 02:25:28

我使用模板(在 Boost.Fusion 的帮助下)为我正在开发的超图库实现类型安全的整数。 我有一个(超)边 ID 和一个顶点 ID,它们都是整数。 使用模板,顶点和超边 ID 变成不同的类型,并且在预期使用其中一种时会生成编译时错误。 避免了我在运行时调试时遇到的很多麻烦。

I used templates (with the help of Boost.Fusion) to achieve type-safe integers for a hypergraph library that I was developing. I have a (hyper)edge ID and a vertex ID both of which are integers. With templates, vertex and hyperedge IDs became different types and using one when the other was expected generated a compile-time error. Saved me a lot of headache that I'd otherwise have with run-time debugging.

邮友 2024-07-15 02:25:28

这是一个来自真实项目的示例。 我有这样的 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);

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

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

Here's one example from a real project. I have getter functions like this:

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

And then a variant with the 'default' value. It returns the value for key if it exists, or default value if it doesn't. Template saved me from having to create 6 new functions myself.

template <typename T>
T get(wxString key, const T& defaultValue)
{
    T temp;
    if (getValue(key, temp))
        return temp;
    else
        return defaultValue;
}
浅语花开 2024-07-15 02:25:28

我经常使用的模板是大量容器类、boost 智能指针、scopeguard、一些 STL算法。

我编写模板的场景:

  • 自定义容器
  • 内存管理、在 void * 分配器之上实现类型安全和 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)

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


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

Templates I regulary consume are a multitude of container classes, boost smart pointers, scopeguards, a few STL algorithms.

Scenarios in which I have written templates:

  • custom containers
  • memory management, implementing type safety and CTor/DTor invocation on top of void * allocators
  • common implementation for overloads wiht different types, e.g.

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

which both just call a (local, hidden) helper function

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

Specific algorithms that are independent of the type, as long as the type has certain properties, e.g. binary serialization.

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)

Unlike virtual functions, templates allow more optimizations to take place.


Generally, templates allow to implement one concept or algorithm for a multitude of types, and have the differences resolved already at compile time.

云柯 2024-07-15 02:25:28

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

We use COM and accept a pointer to an object that can either implement another interface directly or via [IServiceProvider](http://msdn.microsoft.com/en-us/library/cc678965(VS.85).aspx) this prompted me to create this helper cast-like function.

// 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;
}
紫南 2024-07-15 02:25:28

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

I use templates to specify function object types. I often write code that takes a function object as an argument -- a function to integrate, a function to optimize, etc. -- and I find templates more convenient than inheritance. So my code receiving a function object -- such as an integrator or optimizer -- has a template parameter to specify the kind of function object it operates on.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文