编写数学类/函数的最佳实践是什么?

4
我正在将一些算法实现到一个现有程序中。简而言之,我创建了一个新类“Adder”。Adder是另一个表示实际执行计算的物理对象的成员,该对象使用其参数(仅为要进行数学运算的对象列表)调用adder.calc()
为了进行这些数学运算,我需要一些参数,在类外部不存在(但可以设置,请参见下文)。它们既不是配置参数,也不是其他类的成员。这些参数是D1和D2,距离,以及三个固定大小的数组:alpha,beta,delta。
我知道你们中的一些人更喜欢阅读代码而不是阅读文字,所以给你们提供以下代码片段:
class Adder
{
public:

  Adder();
  virtual Adder::~Adder();

void set( float d1, float d2 );
  void set( float d1, float d2, int alpha[N_MAX], int beta[N_MAX], int delta[N_MAX] );

  // Snipped prototypes
  float calc( List& ... );
  // ...

  inline float get_d1() { return d1_ ;};
  inline float get_d2() { return d2_ ;};

private:

  float d1_;
  float d2_;

  int alpha_[N_MAX]; // A #define N_MAX is done elsewhere
  int beta_[N_MAX]; 
  int delta_[N_MAX]; 
};

由于这个对象被用作另一个类的成员,因此它在*.h文件中声明:

private:
  Adder adder_;

通过这样做,我无法直接在构造函数中初始化数组(alpha/beta/delta)(int T[3] = {1, 2, 3};),而不必遍历这三个数组。 我考虑将它们放入 static const 中,但我认为这不是解决此类问题的正确方法。
我的第二个猜测是使用构造函数来初始化数组:
Adder::Adder()
{
    int alpha[N_MAX] = { 0, -60, -120, 180, 120,  60 };
    int beta[N_MAX] = { 0,   0,    0,   0,   0,   0 };
    int delta[N_MAX]  = { 0,   0,  180, 180, 180,   0 };

    set( 2.5, 0, alpha, beta, delta );
}

void Adder::set( float d1, float d2 ) {
    if (d1 > 0)
        d1_ = d1;

    if (d2 > 0)
        d2_ = d2;
}

void Adder::set( float d1, float d2, int alpha[N_MAX], int beta[N_MAX], int delta[N_MAX] ) {
    set( d1, d2 );

    for (int i = 0; i < N_MAX; ++i) {
        alpha_[i] = alpha[i];
        beta_[i] = beta[i];
        delta_[i] = delta[i];
    }
}

是否使用另一个函数(init())来初始化数组会更好?或者有更好的方法吗?

你是否注意到了一些错误或不良实践?


@Isaac - 数组的内容是否总是相同的,还是您希望能够更改它们的内容? - ChrisBD
在声明中,您使用了 6 而非 N_MAX。(无论如何,您都不能传递数组,它们会降级为指针。)我认为您尝试在构造函数中以某种方式初始化数组是行不通的。您将不得不逐个分配每个成员(可能在循环中)。 - sbi
@ChrisBD:请看第二个Adder::set() - sbi
@ChrisBD:如果你的意思是在编译时设置它们,我可以允许这样做。 - Isaac Clarke
@Sbi:这里的“6”是打错了。我的代码可以在Visual C++ 6下编译和运行 ;) 关于函数调用,我应该只传递一个要复制的数组的引用吗? - Isaac Clarke
你可以提供一个默认构造函数,将所有内容初始化为一些合理的值,并提供一个带参数的构造函数,允许用户在构造时填充对象。 - johnsyweb
4个回答

6
你选择了一个非常广泛的主题,因此这里提供一个更广泛的答案。
- 注意你的环境
在代码库中,经常会看到某个代码与其他地方做相同的事情。确保你要解决的问题还没有被你的团队成员或前任解决过。
- 尽量不要重复造轮子
这是我之前提到的一个延伸。
虽然每个人都应该写一个链表或字符串类作为练习,但在生产代码中没有必要这样做。你将拥有MFC、OWL、STL、Boost等工具。如果已经有轮子发明过,那就使用它,并开始编写解决实际问题的代码!
- 考虑如何测试你的代码 测试驱动开发(TDD) 是一种(但不是唯一的)确保你的代码既可测试又经过测试的方法。如果从一开始就考虑测试你的代码,那么测试将非常容易。接着进行测试吧!
  • 编写SOLID代码

维基百科页面比我更好地解释了这一点。

  • 确保你的代码可读性

有意义的标识符只是一个开始。不必要的注释也会降低可读性,长函数、带有长参数列表的函数(例如第二个setter)等也会影响可读性。如果你有编码规范,请遵守它们。

  • 更多地使用const

我对C++的主要抱怨是默认情况下事物不是const!在你的示例中,你的getter应该声明为const,而你的setter应该将它们的参数作为const传递(对于数组使用const-引用)。

  • 确保你的代码可维护性

单元测试(如上所述)将确保下一个更改你的代码的人不会破坏你的实现。

  • 确保你的库可用

如果你遵循最小惊讶原则并使用单元测试记录你的库,那么使用它的程序员将会遇到更少的问题。

这是前一点的延伸。尽一切可能减少代码重复。今天我目睹了一个bug修复需要在15个不同的地方执行(但只有13个地方执行了)。


1
关于单元测试,我已经编写并成功运行了所有Adder类的测试。我的问题在于清晰度、可重用性和可维护性(这也是您所涵盖的)。再次感谢您 ;) - Isaac Clarke

4
创建一个对象时,我建议始终为用户提供完整的对象,并正确初始化所有成员。使用Init方法会导致常见错误,即在两阶段初始化对象中未调用初始化函数。
为了避免这种情况,可以将构造函数设置为私有并使用生成器函数或工厂,这些函数将访问init方法,或将init设置为私有并在构造函数中使用它。后一种建议通常与在构造函数中进行初始化相同,但它允许多个构造函数使用相同的初始化操作。

我完全同意你的第一段:这就是我想要避免使用我上面的第二种方法。一个私有的init()方法和set()会不会多余呢? - Isaac Clarke
@Isaac:如果通过调用init()来实现set(),谁会在意呢? - sbi

1

好的。

  1. 通过在构造函数中使用memset将所有值清除为0(或其他某个值)来设置数组为已知状态,以便在使用之前进行初始化。
  2. 更改构造函数以允许传递可用于将数组初始化为其他值的数组指针。
  3. 保留你现有的 Set 函数来更改你正在使用的数组和变量中的值。

谢谢你的回答!我会重载我的默认构造函数。 - Isaac Clarke

1
  1. 除非你的设计确实需要它们,否则不要使用虚函数。
  2. 在一个主要用于执行一个函数的类中,该函数的规范名称为operator()。例如,您将调用adder_(params),而不是adder_.calc(params)
  3. 如果您正在初始化三个数组,则使用三个for循环更有效率(缓存友好)。

  1. 我的老师总是将他的析构函数声明为虚函数。我想我从他那里继承了这个习惯。(注:pun intended :P)。
  2. 哦!我从未想过那个。不过我担心可读性。我正在使用的C++非常类似于C语言(printf,char*等)。
  3. 我认为我最缺乏的就是那些优化技巧。谢谢你提醒我!
- Isaac Clarke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接