重用对象与创建新对象

Reuse object vs. creating new object

本文关键字:对象 新对象 创建      更新时间:2023-10-16

我们的一个项目处理大量数据。它从数据库中选择数据,并将结果序列化为 JSON/XML。

有时,所选行的数量可以轻松达到 5000 万大关。

然而,该程序的运行时一开始很糟糕。

因此,我们通过一项重大调整重构了程序:

不会为每一行重新创建序列化的工作对象,而是清除并重新初始化该对象。

例如:

以前:

对于每个数据库行,我们创建一个 DatabaseRowSerializer 的对象并调用特定的序列化函数。

// Loop with all dbRows
{
DatabaseRowSerializer serializer(dbRow);
result.add(serializer.toXml());
}

后:

DatabaseRowSerializer 的构造函数不设置 dbRow。相反,这将由 initDbRow((-函数完成。

这里最主要的是,整个运行时只会使用一个对象。在 dbRow 的序列化之后,clear((-函数 将调用以重置对象。

DatabaseRowSerializer serializer;
// Loop with all dbRows
{
serializier.initDbRow(dbRow);
result.add(serializer.toXml());
serializier.clear();
}

所以我的问题:

这真的是解决问题的好方法吗? 在我看来,init((-函数并不是很聪明。通常应该使用构造函数来初始化可能的参数。

您通常更喜欢哪种方式?之前还是之后?

一方面,这是主观的。另一方面,意见广泛同意,在C++中你应该避免这种"init function"成语,因为:

  1. 这是更糟糕的代码

    • 你必须记住"初始化"你的对象,如果你不这样做,它处于什么状态?对象不应处于"死"状态。(不要让我从"移动"对象开始...这就是为什么C++引入构造函数和析构函数的原因,因为旧的 C 方法是一种混合,生成的程序更难证明是正确的。
  2. 这是不必要的

    • 每次创建DatabaseRowSerializer基本上没有开销,除非它的构造函数比你的initDbRow函数做得更多,在这种情况下,你的两个示例无论如何都不等效。

      即使编译器没有优化不必要的"分配",无论如何也没有真正的分配,因为对象只是占用堆栈上的空间,无论如何它都必须这样做。

      因此,如果此更改确实解决了您的性能问题,那么可能还有其他事情发生。

使用构造函数和析构函数。自由而自豪!

这是写C++时的常见建议。


如果出于任何原因确实希望使序列化程序可重用,则可能的第三种方法是将其所有状态移动到实际的操作函数调用中:

DatabaseRowSerializer serializer;
// loop with all dbRows
{
result.add(serializer.toXml(dbRow));
}

如果序列化程序希望缓存信息或重用动态分配的缓冲区以帮助提高性能,则可以执行此操作。这当然会向序列化程序添加一些状态。

如果你这样做并且仍然没有任何状态,那么整个事情可能只是一个静态调用:

// loop with all dbRows
{
result.add(DatabaseRowSerializer::toXml(dbRow));
}

...但它也可能只是一个函数。

最终,我们无法确切知道什么最适合您,但有很多选择和考虑因素。

总的来说,我同意LRiO在另一个答案中提出的观点。

只是将构造函数移出循环并不是一个好主意。

但是,对于这种类型的循环体:

  1. 馈送对象一些数据
  2. 转换对象内的数据
  3. 从对象返回转换后的数据

恕我直言,通常情况下,转换对象将分配一些缓冲区(在堆上(,当使用带有 init 函数的第二个表单时,这些缓冲区可能会被重用。在朴素的实现中,这种重用甚至可能不是故意的,只是实现的副作用。

因此,您看到重构(将对象构造函数提升到循环之外(加快了速度,这可能是因为对象现在能够重用某些缓冲区并避免这些缓冲区的重复"冗余"堆分配。

所以,总结一下:

不希望构造函数本身就被吊出循环。但是,您希望在循环迭代中保留所有可以保留的缓冲区。