【问题标题】:When/Why ( if ever ) should i think about doing Generic Programming/Meta Programming何时/为什么(如果有的话)我应该考虑进行通用编程/元编程
【发布时间】:2010-11-02 02:04:18
【问题描述】:

恕我直言,哎呀,设计模式很有意义,我已经能够实际应用它们。

但是当谈到现代 C++ 类型的“通用编程/元编程”时,我感到很困惑。

-- 它是一种新的编程/设计范式吗?

-- 是否仅限于“库开发”?如果不是,哪些设计/编码情况需要使用元编程/通用编程。

-- 使用模板是否意味着我在进行泛型编程?

我在这个主题上搜索了很多,但没有完全掌握大局。 另请参阅此post


在阅读了这里的讨论之后,到目前为止,我确信(可能仍然不正确):

a) 通用编程和元编程是两个不同的概念。

【问题讨论】:

    标签: c++ templates metaprogramming generic-programming


    【解决方案1】:

    元编程是一个非常奇特的话题。了解它很有趣,它是一个强大的工具,有时您可能会发现它很有用。但它永远不会是您工具箱中最常用的工具。有时,您可能希望您的代码作用于一系列具有不同属性的不相关类型,这就是元编程的用武之地。通过一些技巧,您可以编写一个函数的重载,该函数仅在参数类型是整数时才可用,或者如果它是一个指针,或者它是 X、Y 或 Z 类型(可能忽略 Z 上的常量)。

    本质上是使用类型进行编程。通常,您的程序可以执行诸如取两个数字并产生第三个数字之类的操作,或者告诉您一个数字是否满足某些要求。元编程可以采用两种类型并产生第三种类型,或者告诉您一种类型是否满足某些要求。是的,它可能在图书馆开发中最有用。但话又说回来,大多数代码可以被认为是库代码。您可以说 main() 函数之外的所有内容都是库代码。

    通常,如果您想通过元编程解决问题,您可能需要使用相关的 boost 库来完成繁重的工作。 Boost.TypeTraits 当然还有 Boost.Mpl 可以真正为你简化事情。但这不是你需要知道的东西,也不是你可能经常需要的东西。

    泛型编程是相关的(在某些情况下,可能会在底层使用元编程来变得真正泛型,例如,标准库使用元编程将原始指针转换为有效的迭代器,这是“迭代器”概念所必需的是通用的),但并不完全相同。而且使用范围更广。

    每次实例化std::vector 时,都会使用泛型编程。每次您使用一对迭代器来处理一系列值时,您就使用了泛型编程。泛型编程就是你的代码应该尽可能通用,并且无论放入什么类型都应该工作。 std::vector 不需要包含的类型来实现“ICanBeContained”接口(请记住 Java 是如何要求所有内容都从 Object 派生以便将其存储在容器类中?这意味着原始类型被装箱,并且我们失去了类型安全。这不是通用的,而且是毫无意义的限制。)

    使用迭代器迭代序列的代码是通用的,并且适用于任何类型的迭代器,甚至可以使用普通指针。

    泛型编程非常有用,通常可以在很大程度上替代 OOP。 (见上面的例子。如果我能避免这个限制,我为什么要编写一个需要包含的类型来实现接口的容器?)

    通常,当您在 OOP 中使用接口时,不允许类型在运行时更改(当然这种情况有时也会发生),而是允许您在编译时交换另一种类型(也许在测试期间注入一个模拟对象,而不是使用成熟的实现),或者只是为了解耦两个类。通用编程可以做到这一点,而无需您进行定义和维护接口的繁琐工作。在这些情况下,泛型编程意味着您必须编写和维护更少的代码,并且您可以获得更好的性能和更好的类型安全性。所以,是的,你绝对应该对泛型编程感到宾至如归。 C++ 不是一个很好的 OOP 语言。如果您想严格遵守 OOP,则应该切换到 Java 或其他更偏向于 OOP 的语言。 C++ 允许您编写 OO 代码,但它通常不是最佳解决方案。几乎整个标准库都依赖于泛型编程,而不是 OOP,这是有原因的。标准库中几乎没有继承或多态性。他们不需要它,没有它,代码变得更简单易用,更强大。

    要回答您的其他问题,是的,通用编程几乎是一个独立的范例。模板元编程不是。它是一种用于操作类型系统的相当具体的技术,并且非常擅长解决少数问题。要被认为是一种范式,我认为它必须更普遍有用,并且您可以使用基本上一切的方法,例如函数式、OO 或泛型编程。

    我认为 xtofl 确实做到了:泛​​型编程就是让您的代码不识别类型。 (std::vector 不关心,或者需要知道其中存储了什么类型。它只是工作。)

    另一方面,元编程是关于类型计算的。给定类型 T0 和 T1,我们可以定义一个类型 T2,就像我们可以这样,给定整数 N0 和 N1,我们可以定义一个 N2,它是 N0 和 N1 的和。

    Boost.Mpl 库有一个明显的例子。 在您的正常代码中,如果您有整数 N0、N1 和 N2,您可以创建一个包含这三个值的 std::vector。然后我可以使用其他算法来计算索引,然后提取存储在向量中该位置的值。

    给定类型 T0、T1 和 T2,我们可以创建一个包含这三个类型的 mpl::vector。 我现在可以使用其他算法在编译时计算索引,并提取存储在向量中该位置的 type

    【讨论】:

    • 感谢客观意见显示元和通用之间的差异!
    • 嗯,这就是 理解这两个术语的方式。其他人可能不同意。 (但如果是这样,我相信我们会在 cmets 中听到它;))
    • 这篇文章提出了几点我应该谷歌/探索。感谢您的回复。
    • 不错的帖子,有一点需要注意的是,使用模板进行通用编程仍然通常需要您定义一个“接口”,(即 std::vector 中的 T 必须是可复制的,模板的输入函数可能需要一个参数有一个 foo() 方法等......)它只是不需要你从像 OOP 这样的抽象类继承。
    • 但是你不需要在代码中指定类型是可复制的。如果不是,您只会收到编译器错误。而且,如果您添加一些元编程,您甚至可以对此进行例外处理(例如,迭代器需要公开某些类型定义,但普通指针也可以改装为迭代器)。它通常不那么具有侵入性,并且可以与不是为它设计的现有类型一起工作。 (int 不是为存储在向量中而设计的。由于 Java 中 OOP 的限制,这有点尴尬,但 C++ 中的泛型编程没有问题)
    【解决方案2】:

    您确实必须区分泛型编程(有点不知道类型)和元编程,后者是在类型系统中进行计算的完全合法的方式。

    泛型编程非常在您发现大量代码中的可泛化模式时非常有用。如果模式的差异不仅在于变量值,还在于不同的类型,泛型编程对于重构代码很有用。

    元编程适用于完全不同的领域。确实,该领域非常新,需要您自己进行一些探索。它也有趣的!

    Traits/Policy 概念是一种非常有用且常见的元编程模式。

    【讨论】:

    • 哦,说得好。比我的尝试简洁得多。 +1 ;)
    【解决方案3】:

    C++ 模板元编程是一种强大的代码混淆技术,适用于一系列应用程序:

    • 当您想编写团队中其他人无法理解的代码时
    • 当您需要编写 7 天后仍无法理解的代码时
    • 当代码性能对您来说比可维护性更重要时
    • 当您希望能够将“模板元编程”列为您的简历中的一项技能时。
    • 当您需要编写在许多实际编译器上不太可能工作的代码时
    • 如果您宁愿吃剃须刀片也不愿使用预处理器宏

    另一种情况:

    • 如果您想了解 Boost 库的工作原理,或者您想为它们做出贡献。

    “通用编程”(想想 STL 容器、auto_ptrs 等)之间的区别,这是 C++ 模板旨在完成的功能和“模板元编程”(使用模板系统让编译器有效地“运行算法”)你)很重要。

    我完全赞成第一个,但很难看到后者在现实世界中的好处。

    【讨论】:

    • 听起来太完美了。所以我真的不需要阅读/理解现代 C++。我希望有人说服我否则
    • 你是说宏不是一个好的代码混淆技术??说真的,我认为模板很棒@hotadvice 你绝对应该了解它们。
    • 你可以对 OOP 说同样的话。当人们过度使用类层次结构并将所有内容隐藏在 8 个接口之后并且每个函数都是虚拟的时,您也几乎可以实现上述所有目标。不管怎样,看看 tydok 的例子,告诉我如果 argumnt 类型是 ISwappable 而不是模板类型 T,它会变得更易读和更易于维护。当然不会,这就是泛型编程有用的原因。
    • @ben:我不是反模板,我在自己的代码中广泛使用它们。这是我担心的元编程。你用那个吗?
    • @jalf:谢谢 - 我需要澄清元编程与泛型编程。
    【解决方案4】:

    对于高级模板和技术,我推荐:Modern C++ Design,作者 Andrei Alexandrescu

    C++ 是一门严格的语言,几乎没有运行时自省功能。很多你会遇到的麻烦都可以通过模板来解决。此外,模板语言是图灵完备的,因此可以生成复杂的数据结构并在编译时预先计算值等。 在许多情况下,它可能比它值得的麻烦更多。

    【讨论】:

      【解决方案5】:

      一个尚未给出的答案:尽管泛型编程和元编程确实是彼此完全不同的技术,但它们都旨在解决(本质上)相同的问题:如何摆脱样板。这些也体现在软件工程原则的两个首字母缩写词中:DRY (don't repeat yourself) and DIE (duplication is evil)。这是Occam's Razor 的现代改写,即“实体不得在必要时相乘”(entia non sunt multiplicanda praeter necessitatem)。 [谁知道 14 世纪的逻辑学家可以想出对 21 世纪软件设计有用的东西?]。

      无论如何,这两种技术的重点是找到将“几乎相同”的代码统一为单个参数化代码的方法。在元编程案例中,您正在使用代码生成,因此该技术涉及专门化一段通用代码(也称为模板,或更一般地说是代码生成函数)到特定案例。在泛型编程案例中,您尝试使用类型系统来识别不同的代码段是“相同的”,然后使用基于类型的分派进行专门化。

      事实证明,这两种技术甚至可以一起使用来做一些花哨的事情。有关此示例,请参阅 Multi-stage programming with functors and monads: eliminating abstraction overhead from generic code。但如果你觉得 STL 很吓人的话,还是别看了……

      【讨论】:

        【解决方案6】:

        时间:

        模板元编程是一种编写由编译器解释的程序的方法。它是更高的抽象级别。你可以用它做各种奇怪和时髦的事情。 boost 库包含许多示例:

        • 不喜欢 c++ 是静态类型的吗?使用 boost::any。
        • 喜欢 lambda 演算,但你必须 c++ 吗?使用 boost:lambda。
        • 拥有 EBNF 并需要解析器?使用 boost::spirit。

        为什么:

        这很酷。你可以打动你的约会对象(也许)。

        【讨论】:

          【解决方案7】:

          当你还有很长的时间想要to play时。

          当您需要提升算法并且不希望虚拟函数调用产生开销时,您可以将运行时绑定替换为编译时绑定。

          【讨论】:

          • 现在观看视频。谢谢
          • 玩得开心,我喜欢 Scott Meyers 承认他花了很长时间才完成编译的部分。
          【解决方案8】:

          简单示例:

          template<typename T>
          void
          swap(T& var1, T& var2)
          {
            T var3 = var1;
            var1 = var2;
            var2 = var3;
          }
          
          int a = 2;
          int b = 3;
          swap(a, b);
          
          float c = 400.0f;
          float d = 500.0f;
          swap(c, d);
          

          交换具有相同类型的 2 个变量的值。

          使用模板,我们避免显式地为各种类型的组合创建函数,如下所示:

          void
          swap(int& var1, int& var2)
          {
            int var3 = var1;
            var1 = var2;
            var2 = var3;
          }
          
          void
          swap(float& var1, float& var2)
          {
            float var3 = var1;
            var1 = var2;
            var2 = var3;
          }
          

          上述函数将由编译器自动创建,在我的示例中,如果在代码中的某处使用 intfloat 变量调用 swap()。

          【讨论】:

          • 这只是基本的模板使用。我不会称之为元编程。
          • 基本,是的,但它仍然是元编程。参数也可以是模板类型。
          • 这是一个很好的观点。元编程与“正常”编程并不完全分开。从技术上讲,这个例子元编程。它是如此简单和常用,以至于我们通常不会这么认为。但是,元编程并非必须成为一些晦涩难懂的大师级别的东西,只是用来炫耀你的 C++ 技能并阻止其他人阅读你的代码,这是一个很好的观点。
          • 不同意 - 这是通用模板编程,而不是元编程。
          • 取决于你如何定义这两者。顾名思义,元编程就是当您编写一些能够为您添加代码的代码时。这不就是一个简单的函数模板所做的吗?您编写了一个小规则,编译器可以使用它来生成程序的其余部分。当然,它非常简单,我们通常不会认为它是“元编程”,但从技术上讲,它有什么不同呢?它是元,它是编程。通常,我也会将这两个概念分开(我在自己的回答中也是这样做的),但最终,它们之间的界限是模糊的。
          【解决方案9】:

          我认为,如果您不是库开发人员,那么最好忘记模板元编程。我相信,它在生产代码中毫无用处,带来更多问题,然后是好处:“一旦你开始使用它,你就会失去与这个解决方案相关的灵活性。在你之后很难摆脱模板编译的代码开始使用它。这是一个问题,因为模板不够灵活。

          附:当然我不是指模板容器、交换,...​​而是类似 Alexandrescu 的代码

          【讨论】:

          • 感谢您的回复。从我的小知识和谷歌搜索来看,我同意你的看法。我希望有人不同意并给出反驳意见
          • 我敢说我不同意。我的生产代码在适当的地方使用元编程:使用类型选择器、显式实例化等......
          • 我不明白你的意思。您如何失去灵活性,以及​​如何难以摆脱模板繁重的代码?我想我不同意,但更多的是“我想不出一个这样的例子”。能详细说明一下吗?
          • @xtofl 我同意你的观点,任何教条式的陈述都是错误的。例如,我们还使用显式实例化。类型选择器可能很有用。问题是:“边界在哪里停止?”一段时间后,对我来说,它似乎更接近于琐碎的用途,而不是以前。
          猜你喜欢
          • 2013-10-17
          • 2017-05-17
          • 2011-02-14
          • 2011-05-26
          • 2022-01-24
          • 2013-06-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多