在 class 中内联

Inlining inside a class

我有一个模板化的 class,其中大多数方法都在 class 中定义,因此可以内联它们。在下面的示例中,我展示了一个复制构造函数,但它可以是任何东西。

template <typename T>
class Vector {
private:
    ...
public:
    Vector(const Vector& v) {
        if (condition) {
            call_1(...);
        } else {
            call_2(...);
        }
    }
};

在这个构造函数中,condition 几乎总是正确的,因此我想给编译器所有的提示,以便它内联 call_1 而不是 call_2 (我想防止代码膨胀)。我有两个问题:

  1. 一开始我想定义call_1call_2作为对象的方法,在class里面定义call_1,在外面call_2 class。我还能做些什么来帮助编译器吗?
  2. 如果我想让 call_1call_2 被许多不同的 class 共享并因此将它们定义为 class 之外的函数,我该怎么办?

谢谢。

关注

Is there anything else I could do to help the compiler?

如果condition在编译时已知,可以使用两个构造函数:

Vector(const Vector& v, std::true_type) {
   call_1(...);
}

Vector(const Vector& v, std::false_type) {
   call_2(...);
}

关于

What can I do if I want call_1 and call_2 to be shared by many different objects and therefore define them as functions outside the class?

我认为当您说 许多不同的对象 时,您的意思是 许多不同的 classes。我能想到的选择:

  1. 创建 call_1call_2 全局函数并从所有 class 中调用它们。如果使用它们的 classes 不能被认为具有共同的基类型,那么这种方法是有意义的。

  2. 创建一个公共基础class,并将函数放在基础class中。从派生的 classes 调用它们。仅当基础 class 派生的 class 关系有意义时,此方法才有意义。

所以这有一些不同的方面。

  1. 通常代码膨胀 - 不经常执行的代码不应作为内联代码添加到定期执行的一般代码路径中。
  2. 让编译器知道某些路径比其他路径更有可能。

让我们先看一下一般代码膨胀的细节。它会导致更大的可执行文件,这通常不是一件好事。但是性能开销相对较小 - 如果 vector<T>.push_back 的每个实例都包含相同的大量代码(在大型应用程序中,push_back可能会被调用数千次[我的 11k 行编译器有 70 多次调用,它甚至不怎么使用向量],添加几十条指令当然会导致整个应用程序明显变大。所以有不内联代码绝对有价值。

第二个方面对于编译器 "make the right decision" 的能力很重要。简单地在 class 之外声明一个函数可能会有帮助,但除非编译器也知道它不太可能发生,否则不能保证编译器不会内联反映异常情况的函数。

但是,在技术方面,您唯一的可移植选择是将函数移出 class 定义。这是至少向编译器提示它不适合内联的唯一方法。但是编译器有时会决定 "it's worth inlining this code anyway",而且它是模板代码,你不能真的做“让我们把代码放在一个不与调用者一起编译的源文件中”的老把戏。

使用扩展为 __builtin_expect(expr, likely) 的宏,其中 expr 是您的条件,likely "common outcome"(0 用于 false1 对于 true)。不幸的是,这在 Microsoft 编译器中不可用。您可能还想探索扩展为 "do not inline" 的宏选项,例如GCC 的 __attribute__((noinline)) 强制代码不被内联。

使用profile-driven optimization和相关注释,这在大多数高级编译器中都可用,这将使编译器选择正确的选项。

对于问题 2,一般很难做到这一点 - 编写一些通用代码可能会奏效,例如:

template<T>
T* grow_allocation(T* existing, size_t cur_size, size_t new_size)
{
    T* new_alloc = new T[new_size];
    // There is probably clever stuff like std::copy and std::move
    // that is "better" than this - writing basic loops to clearly
    // show what is being done.
    for(size_t i = 0; i < cur_size; i++)
    {
        new_alloc[i] = existing[i]; 
    }
    for(size_t i = cur_size; i < new_size; i++)
    {
        new_alloc[i] = T();
    }
    // Probably need to deal with "new_size < cur_size" and destroy
    // those too... 
}

现在您可以从所有具有动态分配的内存区域来保存类型 T 的对象的地方调用此函数。 [可以轻松构建]