是否使用预处理器良好做法声明多个相似 类?
Is declaring multiple similar classes with the preprocessor good practice?
假设我想创建一个数学库。我需要在不同维度上操作向量,所以我想每个维度有一个 class(a.k.a Vector2
、Vector3
、Vector4
... )
到目前为止一切顺利。但它会导致严重的代码重复,因为 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
这会大大简化任务,但这是一个好习惯吗?
鉴于您发布的代码,您可以轻松地将其替换为以下内容,而不必使用预处理器技巧。
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;
};
您可以轻松扩展 Vector
和 Data
以支持更多功能。
不仅如此,您还可以在实例化 Vector
时防止 dim
的无效值,方法是确保 Data
仅针对dim
.
功能上的哪些相似性应该导致代码重复?
考虑post标题中的问题,"Is declaring multiple similar classes with the preprocessor good practice?"首先,让我们看看是否应该完全消除重复。是的,您没有看错——shared code can be worse than duplication,取决于重复项的 上下文。
根据经验
- 巧合重复应该保留
- 系统性 应排除重复
请注意 'coincidental' 并不意味着 'accidental'。相反,巧合的重复代码往往是有意识地编写的。关键是相似的功能没有一个潜在的共同原则。因此,即使它们今天碰巧相似,重要的是它们可以独立进化。
另请注意,'systematic' 并不一定意味着您已经为此做好了计划。它与 'systematic error' 中的 'systematic' 相同,即重复 是 由于 潜在的共同主体 ,是否有意识是不是。
向量
应用于您在问题描述中给出的示例,differently-dimensioned 数学库中的矢量类型,可以说很多重复 将 属于系统型,应该淘汰。但在其他情况下,它可能不会切得那么清楚。
如何消除不必要的重复?
我们可以使用的工具
如果它发生在编译时,您在 C 中的选项将受到限制。但是您将问题标记为 C++,尽管 C 和 C++ 共享它们的预编译器,但 C++ 提供了更多功能:内联函数、编译指示和最后但并非最不重要的模板 类和模板函数。
R Sahu's answer 已经很好地展示了如何将模板应用于您的案例。使用模板可以做更多的事情:在 C++ 中,有一整套称为 'template meta programming'.
的开发规则
使用模板解决方案可能比使用 pre-processor 快速获得一些东西要难一些。那么为什么要选择模板而不是 pre-processor 宏和包含呢?
想想你的(图书馆)用户
获得一些 'works' 的东西对于您仅供自己使用且不需要大量维护的代码可能就足够了。但是,如果您正在编写一个库,那么当您的用户在使用它时出现可检测到的错误时,他们会希望得到编译器的通知。作为必然结果,您会想知道当以编译无错的方式调用时,库会做一些有意义的事情。这对于 non-trivial 逻辑的宏来说是非常困难的,因为你必须考虑几个问题:
- 保障型安全
- 如果宏带有参数,这些参数可以再次成为宏吗?
- 如果宏带有参数,这些参数可以是函数或方法调用吗?
- 如果这些函数有副作用怎么办? (你必须小心你的宏,即使它可能多次使用函数的 return 值,也不会多次调用函数。)
- 宏没有命名空间,因此您必须通过 name-prefixing 约定来避免冲突
虽然最初使用模板可能更难获得有效的东西,但比使用 makros 获得有效的东西要容易得多 .
不,这不是好的做法。出于其他答案中提到的原因,我建议不要这样做(使用模板可能是更好的选择)。也就是说,预处理器和宏在某些情况下可能会派上用场。我已经成功地使用了宏,我知道类似 类 的代码很小,不太可能更改(声明和实现)或被继承,并且使用模板实现会更难实现write/understand,因此更容易出错。确实,这段代码到现在还没有再动过,运行很流畅,我还是觉得宏是最好的选择。
不过,根据问题中提供的信息,我认为您不属于其中一种情况。但是使用任何对你有用的东西,特别是如果它是一个个人项目:如果它在未来咬你,你就会学到一些东西。 ;-)
假设我想创建一个数学库。我需要在不同维度上操作向量,所以我想每个维度有一个 class(a.k.a Vector2
、Vector3
、Vector4
... )
到目前为止一切顺利。但它会导致严重的代码重复,因为 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
这会大大简化任务,但这是一个好习惯吗?
鉴于您发布的代码,您可以轻松地将其替换为以下内容,而不必使用预处理器技巧。
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;
};
您可以轻松扩展 Vector
和 Data
以支持更多功能。
不仅如此,您还可以在实例化 Vector
时防止 dim
的无效值,方法是确保 Data
仅针对dim
.
功能上的哪些相似性应该导致代码重复?
考虑post标题中的问题,"Is declaring multiple similar classes with the preprocessor good practice?"首先,让我们看看是否应该完全消除重复。是的,您没有看错——shared code can be worse than duplication,取决于重复项的 上下文。
根据经验
- 巧合重复应该保留
- 系统性 应排除重复
请注意 'coincidental' 并不意味着 'accidental'。相反,巧合的重复代码往往是有意识地编写的。关键是相似的功能没有一个潜在的共同原则。因此,即使它们今天碰巧相似,重要的是它们可以独立进化。
另请注意,'systematic' 并不一定意味着您已经为此做好了计划。它与 'systematic error' 中的 'systematic' 相同,即重复 是 由于 潜在的共同主体 ,是否有意识是不是。
向量
应用于您在问题描述中给出的示例,differently-dimensioned 数学库中的矢量类型,可以说很多重复 将 属于系统型,应该淘汰。但在其他情况下,它可能不会切得那么清楚。
如何消除不必要的重复?
我们可以使用的工具
如果它发生在编译时,您在 C 中的选项将受到限制。但是您将问题标记为 C++,尽管 C 和 C++ 共享它们的预编译器,但 C++ 提供了更多功能:内联函数、编译指示和最后但并非最不重要的模板 类和模板函数。
R Sahu's answer 已经很好地展示了如何将模板应用于您的案例。使用模板可以做更多的事情:在 C++ 中,有一整套称为 'template meta programming'.
的开发规则使用模板解决方案可能比使用 pre-processor 快速获得一些东西要难一些。那么为什么要选择模板而不是 pre-processor 宏和包含呢?
想想你的(图书馆)用户
获得一些 'works' 的东西对于您仅供自己使用且不需要大量维护的代码可能就足够了。但是,如果您正在编写一个库,那么当您的用户在使用它时出现可检测到的错误时,他们会希望得到编译器的通知。作为必然结果,您会想知道当以编译无错的方式调用时,库会做一些有意义的事情。这对于 non-trivial 逻辑的宏来说是非常困难的,因为你必须考虑几个问题:
- 保障型安全
- 如果宏带有参数,这些参数可以再次成为宏吗?
- 如果宏带有参数,这些参数可以是函数或方法调用吗?
- 如果这些函数有副作用怎么办? (你必须小心你的宏,即使它可能多次使用函数的 return 值,也不会多次调用函数。)
- 宏没有命名空间,因此您必须通过 name-prefixing 约定来避免冲突
虽然最初使用模板可能更难获得有效的东西,但比使用 makros 获得有效的东西要容易得多 .
不,这不是好的做法。出于其他答案中提到的原因,我建议不要这样做(使用模板可能是更好的选择)。也就是说,预处理器和宏在某些情况下可能会派上用场。我已经成功地使用了宏,我知道类似 类 的代码很小,不太可能更改(声明和实现)或被继承,并且使用模板实现会更难实现write/understand,因此更容易出错。确实,这段代码到现在还没有再动过,运行很流畅,我还是觉得宏是最好的选择。
不过,根据问题中提供的信息,我认为您不属于其中一种情况。但是使用任何对你有用的东西,特别是如果它是一个个人项目:如果它在未来咬你,你就会学到一些东西。 ;-)