使用预处理器的良好实践声明多个类似的类?

时间:2015-04-16 22:10:25

标签: c++ c-preprocessor

我们想说我想创建一个数学库。我需要在不同的维度操纵向量,所以我希望每个维度有一个类(a.k.a Vector2Vector3Vector4 ...)

到目前为止一切顺利。但它会导致严重的代码重复,因为Vector3主要是Vector2,在某些函数中使用z属性。

所以我有了一个主意。代码复制是机器而不是人类的任务,所以我可以这样写:

在Vector.hpp中:

#ifndef VECTOR_HPP
#define VECTOR_HPP

#define VECTOR_DIM 2
#include "_Vector.hpp"
#define VECTOR_DIM 3
#include "_Vector.hpp"
#define VECTOR_DIM 4
#include "_Vector.hpp"
#undef VECTOR_DIM

#endif

在_Vector.hpp中:

// This header was not protected from multiple inclusions on purpose

#define VECTOR_NAME Vector ## VECTOR_DIM

class VECTOR_NAME
{
public:
    // Some methods here ...

    float x;
    float y;
#if VECTOR_DIM >= 3
    float z;
#endif
#if VECTOR_DIM >= 4
    float w;
#endif
};

#undef VECTOR_NAME

这会简化任务,但这是一种很好的做法吗?

3 个答案:

答案 0 :(得分:7)

鉴于您发布的代码,您可以使用以下代码轻松替换它,而不必使用预处理器技巧。

template <int dim> struct Data;

template <> struct Data<2>
{
   float x;
   float y;
};

template <> struct Data<3> : Data<2>
{
   float z;
};

template <> struct Data<4> : Data<3>
{
   float w;
};

template <int dim> class Vector
{
   public:
      // Some methods here ...
      Data<dim> data;
};

您可以轻松扩展VectorData以支持更多功能。

不仅如此,您还可以在实例化dim时阻止Vector的无效值,方法是确保Data仅为dim的预定义有效值定义{{1}} 1}}。

答案 1 :(得分:1)

功能的相似性会导致代码重复吗?

考虑帖子标题中的问题,&#34;用预处理器好的做法声明多个类似的类?&#34;首先,让我们看一下是否应该删除重复 。是的,您已经正确阅读了shared code can be worse than duplication,具体取决于重复的背景

根据经验

  • 巧合重复应保留
  • 系统性重复应该被考虑在内

注意&#39;巧合&#39;并不意味着偶然的&#39;。相反,偶然重复的代码通常会有意识地写出来。关键点在于,类似的功能没有潜在的共同主体。因此,即使它们碰巧与日相似,但它们可以独立发展也很重要。

另请注意,“系统性”&#39;并不意味着你已经计划好了。它是相同的,系统的&#39;如系统错误&#39;,即由于潜在的共同主体而重复 ,无论是否有意识地这样做。

载体

应用于您在问题描述中给出的示例,数学库中不同尺寸的矢量类型,它可能保存为说很多重复属于系统类型应该被淘汰。但在其他情况下,可能不会那么明确。

如何消除不需要的重复?

我们可以使用的工具

如果它将在编译时发生,那么 C 中的选项将受到限制。但是你将这个问题标记为 C ++ ,即使C和C ++共享他们的预编译器,C ++也必须提供更多:内联函数,编译指示以及最后但并非最不重要的模板类和模板函数。

R Sahu's answer已经很好地展示了如何将模板应用于您的案例。使用模板可以实现更多功能:在C ++中,有一个完整的开发规程,称为模板元编程&#39;。

让模板解决方案工作起来比使用预处理器快速完成某些工作要困难得多。那么为什么要选择模板而不是支持处理器的宏呢?

想想你的(图书馆)用户

获得有用的东西&#39;对于您自己使用的代码而言,这可能就足够了,并且不需要大量维护。但是,如果您正在编写库,那么当用户在使用它时发现可检测到的错误时,您的用户将希望得到编辑的通知。作为必然结果,您将知道在以无错误编译的方式调用时,库会执行一些有意义的操作。对于非平凡的逻辑来说,使用宏是非常困难的,因为你必须考虑几个问题:

  • 保证类型安全
  • 如果宏接受参数,那些参数又可以是宏吗?
  • 如果宏接受参数,那些参数可以是函数还是方法调用?
    • 如果这些功能有副作用怎么办? (你必须小心你的宏,即使它可能会多次使用函数&#39;返回值,也不会多次调用这些函数。)
  • 宏不是命名空间,因此您必须通过名称前缀约定来避免冲突

虽然模板最初可能更难以获得一些可行的东西,但是使用makros比获得正确正确的东西要容易得多。

答案 2 :(得分:0)

不,这不是好习惯。由于其他答案中提到的原因,我建议不要这样做(使用模板可能是更好的选择)。话虽这么说,预处理器和宏可能会在某些情况下派上用场。我已经成功地使用了宏,在这种情况下,我知道代码类似的类很小,不太可能改变(声明和实现)或继承,并且使用模板实现将是一个更难写/理解,因此更容易出错。实际上,这段代码仍然没有被触及,工作顺利,我仍然认为宏是最好的选择。

尽管根据问题中提供的信息,我并不认为你处于这种情况之一。但是使用适合你的任何东西,特别是如果它是个人项目:如果它在将来咬你,你就会学到一些东西。 ;-)