我发现 模板元编程 5年多前,从阅读中获得了巨大的乐趣 现代 C++ 设计 但我从未找到在现实生活中使用它的机会。

在实际代码中使用过这种技术吗?

贡献者 促进 无需申请;o)

有帮助吗?

解决方案

我曾经使用 C++ 中的模板元编程来实现一种称为“符号扰动”的技术,用于处理几何算法中的简并输入。通过将算术表达式表示为嵌套模板(即基本上通过手动写出解析树)我能够将所有表达式分析交给模板处理器。

使用模板执行此类操作比使用对象编写表达式树并在运行时进行分析更有效。它速度更快,因为修改后的(扰动的)表达式树随后可在与代码的其余部分相同的级别上供优化器使用,因此您可以获得优化的全部好处,无论是在表达式内,还是在表达式和表达式之间(如果可能)。周围的代码。

当然,您可以通过为您的表达式实现一个小型 DSL(领域特定语言)并将翻译后的 C++ 代码粘贴到您的常规程序中来完成同样的事情。这将为您带来所有相同的优化优势,并且更加清晰——但代价是您必须维护一个解析器。

其他提示

我发现现代 C++ 设计中描述的策略在两种情况下非常有用:

  1. 当我开发一个组件时,我希望能够重用该组件,但方式略有不同。Alexandrescu的建议是使用策略来反映设计非常适合这里的建议 - 它可以帮助我获得过去的问题:“我可以使用背景线程来做到这一点,但是如果以后有人想及时地做到这一点,该怎么办?”好的,好的,我只是写我的班级以接受contrencypolicy并目前实现我需要的班级。然后至少我知道我身后的人可以在需要时编写并插入新策略,而不必完全重新设计我的设计。警告:有时我必须控制自己,否则事情可能会失控——记住 亚格尼 原则!

  2. 当我尝试将几个相似的代码块重构为一个时。通常,代码会被复制粘贴并稍微修改,因为否则它会有太多的 if/else 逻辑,或者因为涉及的类型太不同。我发现策略通常允许一个干净的通用版本,而传统逻辑或多重继承则不允许。

我在游戏图形代码的内部循环中使用了它,您需要一定程度的抽象和模块化,但无法支付分支或虚拟调用的成本。总的来说,这是一个比大量手写特殊情况函数更好的解决方案。

模板元编程和表达式模板作为优化方法在科学界变得越来越流行,这些优化方法可以将一些计算工作转移到编译器上,同时保持一些抽象。生成的代码较大且可读性较差,但我已使用这些技术来加速 FEM 库中的线性代数库和求积方法。

对于特定应用的阅读, 托德·维尔德赫伊曾 是这个领域的大人物。一本很受欢迎的书是 面向科学家和工程师的 C++ 和面向对象数值计算 杨道奇.

编写 C++ 时,模板元编程是一种奇妙且强大的技术 图书馆. 。我在自定义解决方案中使用过它几次,但通常不太优雅的旧式 C++ 解决方案更容易通过代码审查,并且更容易为其他用户维护。

然而,在编写可重用的组件/库时,我在模板元编程中取得了很多进展。我不是在谈论 Boost 的一些大的东西,只是会经常重用的小组件。

我将 TMP 用于单例系统,用户可以指定他们想要的单例类型。界面非常基本。其下方由重型 TMP 提供动力。

template< typename T >
T& singleton();

template< typename T >
T& zombie_singleton();

template< typename T >
T& phoenix_singleton();

另一个成功的用途是简化我们的 IPC 层。它是使用经典的 OO 风格构建的。每个消息都需要从抽象基类派生并重写一些序列化方法。没什么太极端的,但它会生成大量样板代码。

我们在其中添加了一些 TMP,并针对仅包含 POD 数据的消息的简单情况自动生成所有代码。TMP 消息仍然使用 OO 后端,但它们大大减少了样板代码的数量。TMP 还用于生成消息访问者。随着时间的推移,我们所有的消息都迁移到了 TMP 方法。构建一个简单的 POD 结构仅用于消息传递并添加让 TMP 生成类所需的几行(可能是 3 行)比派生一条新消息以通过 IPC 发送常规类更容易且代码更少框架。

我一直使用模板元编程,但在 D 中,而不是 C++ 中。C++ 的模板元语言最初是为简单类型参数化而设计的,几乎是偶然地成为图灵完备的元语言。因此,它是一个只有安德烈·亚历山德雷斯库(Andrei Alexandrescu)才能使用的图灵油坑,而不仅仅是凡人。

另一方面,D 的模板子语言实际上是为超越简单类型参数化的元编程而设计的。安德烈·亚历山德雷斯库 看来很喜欢, 但其他人其实可以理解他的D模板。它也足够强大,以至于有人写了一个 编译时光线追踪器 作为概念证明。

我想我在 D 中编写的最有用/最重要的元程序是一个函数模板,它给定一个结构类型作为模板参数,并按照与结构中的变量声明相对应的顺序给出列标题名称列表作为运行时参数,将读取 CSV 文件,并返回一个结构体数组,每一行一个结构体数组,每个结构体字段对应一列。所有类型转换(字符串到浮点数、整数等)都是根据模板字段的类型自动完成的。

另一个好的模板是深度复制函数模板,它可以正确处理结构、类和数组,它在大多数情况下都可以工作,但仍然不能正确处理少数情况。它仅使用编译时反射/内省,因此它可以使用结构,与成熟的类不同,结构在 D 中没有运行时反射/内省功能,因为它们应该是轻量级的。

大多数使用模板元编程的程序员通过像 boost 这样的库间接使用它。他们甚至可能不知道幕后发生了什么,只知道它使某些操作的语法变得更加容易。

我在 DSP 代码中经常使用它,特别是 FFT、固定大小循环缓冲区、hadamard 变换等。

对于那些熟悉 Oracle 模板库的人 (OTL), boost::任何和 洛基 库(现代 C++ 设计中描述的库)这是概念验证 TMP 代码,使您能够将一行 otl_stream 存储在 vector<boost::any> 容器并按列号访问数据。'是的',我将把它合并到生产代码中。

#include <iostream>
#include <vector>
#include <string>
#include <Loki/Typelist.h>
#include <Loki/TypeTraits.h>
#include <Loki/TypeManip.h>
#include <boost/any.hpp>
#define OTL_ORA10G_R2
#define OTL_ORA_UTF8
#include <otlv4.h>

using namespace Loki;

/* Auxiliary structs */
template <int T1, int T2>
struct IsIntTemplateEqualsTo{
    static const int value = ( T1 == T2 );
};

template <int T1>
struct ZeroIntTemplateWorkaround{
    static const int value = ( 0 == T1? 1 : T1 );
};


/* Wrapper class for data row */
template <class TList>
class T_DataRow;


template <>
class T_DataRow<NullType>{
protected:
    std::vector<boost::any> _data;
public:
    void Populate( otl_stream& ){};
};


/* Note the inheritance trick that enables to traverse Typelist */
template <class T, class U>
class T_DataRow< Typelist<T, U> >:public T_DataRow<U>{
public:
    void Populate( otl_stream& aInputStream ){
        T value;
        aInputStream >> value;
        boost::any anyValue = value;
        _data.push_back( anyValue );

        T_DataRow<U>::Populate( aInputStream );
    }

    template <int TIdx>
    /* return type */
    Select<
        IsIntTemplateEqualsTo<TIdx, 0>::value,
        typename T,
        typename TL::TypeAt<
            U,
            ZeroIntTemplateWorkaround<TIdx>::value - 1
        >::Result
    >::Result
    /* sig */
    GetValue(){
    /* body */
        return boost::any_cast<
            Select<
                IsIntTemplateEqualsTo<TIdx, 0>::value,
                typename T,
                typename TL::TypeAt<
                    U,
                    ZeroIntTemplateWorkaround<TIdx>::value - 1
                >::Result
            >::Result
        >( _data[ TIdx ] );
    }
};


int main(int argc, char* argv[])
{
    db.rlogon( "AMONRAWMS/WMS@amohpadb.world" ); // connect to Oracle
    std::cout<<"Connected to oracle DB"<<std::endl;
    otl_stream o( 1, "select * from blockstatuslist", db );

    T_DataRow< TYPELIST_3( int, int, std::string )> c;
    c.Populate( o );
    typedef enum{ rcnum, id, name } e_fields; 
    /* After declaring enum you can actually acess columns by name */
    std::cout << c.GetValue<rcnum>() << std::endl;
    std::cout << c.GetValue<id>() << std::endl;
    std::cout << c.GetValue<name>() << std::endl;
    return 0;
};

对于那些不熟悉上述库的人。

OTL 的 otl_stream 容器的问题是,通过声明适当类型的变量并应用 operator >> 通过以下方式到 otl_stream 对象:

otl_stream o( 1, "select * from blockstatuslist", db );
int rcnum; 
int id;
std::string name;
o >> rcnum >> id >> name; 

这并不总是很方便。解决方法是编写一些包装类并用来自 otl_stream 的数据填充它。我们的愿望是能够声明列类型列表,然后:

  • 取列的类型T
  • 声明该类型的变量
  • 申请 olt_stream::operator >>(T&)
  • 存储结果(在 boost::any 的向量中)
  • 获取下一列的类型并重复,直到处理完所有列

你可以在洛基的帮助下完成这一切 Typelist 结构体、模板特化和继承。

借助 Loki 的库结构,您还可以生成一堆 GetValue 函数,这些函数返回适当类型的值,从列号(实际上是 Typelist).

不,我还没有在生产代码中使用它。

为什么?

  1. 我们必须支持 6+ 平台 本国的 平台 编译器。在这种环境中使用STL很难,更不用说现代模板技术了。
  2. 开发人员似乎不再跟上 C++ 的进步。我们必须使用C ++。我们有遗留代码和遗留设计。新代码是在其他事情中完成的,例如Java,JavaScript,Flash。

在问这个问题后将近 8 个月,我终于使用了一些 TMP,我使用了 类型列表 接口以便在基类中实现 QueryInterface。

我将它与 boost::statechart 一起用于大型状态机。

是的,当我将遗留 API 封装在更现代的 C++ 接口中时,我主要是做一些类似于鸭子类型的事情。

不要那样做。其背后的原因如下:根据模板元编程的本质,如果逻辑的某些部分是在编译时完成的,那么它所依赖的每个逻辑也必须在编译时完成。一旦启动它,就在编译时执行一部分逻辑,没有返回。雪球会一直滚下去,没有办法阻止。

例如,您无法迭代 boost::tuple<> 的元素,因为您只能在编译时访问它们。您必须使用模板元编程来实现简单直接的 C++ 功能,而这 总是 当 C++ 用户不够小心,没有将太多内容移至编译时时,就会发生这种情况。有时很难看出编译时逻辑的某种使用何时会出现问题,有时程序员渴望尝试和测试他们在 Alexandrescu 中读到的内容。无论如何,我认为这是一个非常糟糕的主意。

许多程序员很少使用模板,因为直到最近编译器支持还很差。然而,虽然模板在过去存在很多问题,但较新的编译器有更好的支持。我编写的代码必须与 Mac 和 Linux 上的 GCC 以及 Microsoft Visual C++ 一起使用,只有在 GCC 4 和 VC++ 2005 上这些编译器才能很好地支持该标准。

通过模板进行通用编程并不是您一直需要的东西,但绝对是您工具箱中有用的代码。

显而易见的示例容器类但模板对于许多其他事情也很有用。我自己工作中的两个例子是:

  • 智能指针(例如引用计数、写时复制等)
  • 数学支持类,例如矩阵、向量、样条线等。需要支持多种数据类型并且仍然高效。
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top