在C+;中避免循环中复杂对象的最小范围无效性的技术+;? 问题一 C++中有一个优雅的解决方案,以防止一个必须声明复杂的对象变量,这些变量只在循环之外的循环中使用,原因是? 详细说明

在C+;中避免循环中复杂对象的最小范围无效性的技术+;? 问题一 C++中有一个优雅的解决方案,以防止一个必须声明复杂的对象变量,这些变量只在循环之外的循环中使用,原因是? 详细说明,c++,performance,loops,C++,Performance,Loops,一位同事提出了一个有趣的观点。对于我们的代码策略,它声明(意译):始终为变量使用最小范围,并在第一次初始化时声明变量 编码指南示例: // [A] DO THIS void f() { ... for (int i=0; i!=n; ++i) { const double x = calculate_x(i); set_squares(i, x*x); } ... } // [B] DON'T do this: void f() { int i; int

一位同事提出了一个有趣的观点。对于我们的代码策略,它声明(意译):始终为变量使用最小范围,并在第一次初始化时声明变量

编码指南示例:

// [A] DO THIS
void f() {
  ...
  for (int i=0; i!=n; ++i) {
    const double x = calculate_x(i);
    set_squares(i, x*x);
  }
  ...
}

// [B] DON'T do this:
void f() {
  int i;
  int n;
  double x;
  ...
  for (i=0; i!=n; ++i) {
    x = calculate_x(i);
    set_squares(i, x*x);
  }
  ...
}
这一切都很好,当然也没有什么错,除非你从基本类型转移到对象。(对于某种类型的接口)

例如:

// [C]
void fs() {
  ...
  for (int i=0; i!=n; ++i) {
    string s;
    get_text(i, s); // void get_text(int, string&);
    to_lower(s);
    set_lower_text(i, s);
  }
  ...
}
在这里,字符串s将被销毁,它的内存将在每个循环周期释放,然后每个循环
get_text
函数都必须为s缓冲区新分配内存

写下以下内容显然更有效:

  // [D]
  string s;
  for (int i=0; i!=n; ++i) {
    get_text(i, s); // void get_text(int, string&);
    to_lower(s);
    set_lower_text(i, s);
  }
现在,s缓冲区中分配的内存将在循环运行之间保留,我们很可能会节省分配的内存

免责声明:<强>请注意:< /强>因为这是循环,我们在讨论内存分配,我做的是<强>不/强>考虑它“强>早熟优化< /强>来大致考虑这个问题。当然,有些情况和循环的开销并不重要;但是,

n
的唠叨倾向于比开发人员最初期望的更大,并且代码的唠叨倾向于在性能确实重要的上下文中运行

无论如何,现在对于“通用”循环构造来说,更有效的方法是违反代码局部性,将复杂对象声明到不合适的位置,“以防万一”。这让我相当不安

请注意,我认为这样写:

// [E]
void fs() {
  ...
  {
    string s;
    for (int i=0; i!=n; ++i) {
      get_text(i, s); // void get_text(int, string&);
      to_lower(s);
      set_lower_text(i, s);
    }
  }
  ...
}
没有解决方案,因为可读性受到的影响更大

进一步思考
get_text
函数的界面无论如何都不是惯用的,因为out参数无论如何都是如此,一个“好”的界面将按值返回:

  // [F]
  for (int i=0; i!=n; ++i) {
    string s = get_text(i); // string get_text(int);
    to_lower(s);
    set_lower_text(i, s);
  }
这里,我们不会为内存分配支付双倍的费用,因为极有可能通过RVO从返回值构造
s
,因此对于[F],我们支付与[C]相同的分配开销。但是,与[C]情况不同,我们无法优化此接口变体

<> P>><强>底线<强>似乎是使用最小范围(can)损害性能和使用干净的接口,我至少考虑按价值返回比清理REF PARAM的东西要干净很多——至少在一般情况下是这样的。

问题并不在于有时为了提高效率而不得不放弃干净的代码,问题在于一旦开发人员开始发现这种特殊情况,整个编码指南(见[A],[B])就会失去权威

现在的问题是:见第一段,我想:

  • 为这些重量级人物破例。如“D”,请注意,您可以根据需要限制范围
  • 允许使用辅助函数(字符串也可以是参数)
  • 如果你真的不喜欢这些,你可以在
    中为
    循环的作用域声明一个局部变量,使用一个包含计数器/迭代器和临时变量的多元素对象
    std::pair
    将是一种选择,尽管专用容器可以减少语法噪音
(在许多情况下,out参数比RVO样式更快)

写:[示例D的开头…]显然更有效

我对此表示怀疑。您要为默认构建付费,从循环外开始。在循环中,
get_text
可能调用重新分配缓冲区(取决于
get_text
字符串的定义方式)。请注意,对于某些运行,您实际上可能会看到性能的改进(例如,字符串逐渐变短的情况),而对于某些运行(每次迭代时字符串长度增加约2倍),性能会受到巨大的影响


如果不变量造成瓶颈(探查器会告诉您),将其从循环中取出是非常有意义的。否则,选择惯用的代码。

如果您有string类的写时复制实现,那么to_lower(s)将分配内存,因此不清楚您是否可以通过在循环外简单地声明s来获得性能

我认为有两种可能性: 1.)你有一个类,它的构造函数做了一些非平凡的事情,在每次迭代中都不需要重新做。然后,从逻辑上讲,将声明放在循环之外是很简单的。 2.)您有一个类,其构造函数不做任何有用的事情,然后将声明放入循环中

如果1。如果为true,那么您可能应该将对象拆分为一个助手对象(例如,分配空间并进行非平凡初始化)和一个flyweight对象。如下所示:

StringReservedMemory m (500); /* base object for something complex, allocating 500 bytes of space */
for (...) {
   MyOptimizedStringImplementation s (m);
   ...
}

取决于
get_text
的实现

如果您可以实现它,使它在大部分时间内重用字符串对象中分配的空间,那么一定要在循环外部声明该对象,以避免在每次循环迭代时分配新的动态内存

动态分配是昂贵的(最好的单线程分配器在一次分配中需要大约40条指令,多线程会增加开销,而且并非所有分配器都是“最佳的”),并且会分割内存

(顺便说一句,
std::string
通常实现所谓的“小字符串优化”,这避免了对小字符串的动态分配。因此,如果您知道大多数字符串都足够小,并且
std::string
的实现不会改变,那么即使在每次迭代中构造新对象,理论上也可以避免动态分配。这