15

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

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

作为贿赂,期待您的回答获得赞成票。

4

12 回答 12

12

模板的一般信息:

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

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

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

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

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

现在到很酷的东西:

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

查看来自 Wikipedia 的模板元编程示例。它展示了如何在编译时使用模板来执行代码。因此在运行时你有一个预先计算好的常数。

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
}
于 2008-10-26T00:56:05.487 回答
8

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

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

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

于 2008-10-26T01:33:23.070 回答
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 ;
于 2008-10-26T11:13:44.787 回答
4

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

于 2008-10-26T12:39:55.657 回答
3

这是一个真实项目的例子。我有这样的吸气剂功能:

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

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

template <typename T>
T get(wxString key, const T& defaultValue)
{
    T temp;
    if (getValue(key, temp))
        return temp;
    else
        return defaultValue;
}
于 2008-10-26T13:04:51.287 回答
2

我经常使用的模板是大量的容器类、boost 智能指针、范围保护、一些 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)

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


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

于 2008-10-26T01:48:02.120 回答
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;
}
于 2008-10-26T12:00:00.230 回答
1

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

于 2008-10-26T02:22:11.193 回答
1

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

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

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

于 2008-10-26T12:14:18.767 回答
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) ;
}

等等。

这有点劫持模板的东西,但最后,我想它比使用类型定义的函数指针或使用宏要好。

于 2008-10-26T15:10:29.817 回答
1

已经提到您可以使用模板作为策略类来做某事。我经常使用这个。

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

于 2008-10-27T14:31:03.170 回答
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<> 派生类型的通用多态函数:

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));
};
于 2008-10-29T05:08:21.887 回答