Use inline
只是为了满足单一定义规则。别打扰inline
出于性能原因。
如果内联函数只是为了“为
编译器应用更多优化。”
如果我们谈论的是inline
关键字,那么这是not what inline
是关于。就像,完全一样。
All inline
does确保函数不违反单一定义规则。它还可能向编译器提供提示,提示编译器应该内联吹出代码,这可能会也可能不会提高速度,但编译器有权忽略该提示。事实上,在现代编译器中,他们很多时候都会忽略这个提示。
编译器很可能不会内联吹出代码的情况之一是使用大函数。这并不意味着不应声明该函数inline
然而,这完全取决于函数的声明、定义和使用方式。但话又说回来,它已经nothing与性能有关。
在应用优化技术时,不要试图超越编译器。它在使你的程序运行得更快方面比你要好得多。
让我们看看标准对这一切有何规定。
7.1.2 函数说明符
2/A 函数声明(8.3.5、9.3、11.3)inline
说明符
声明一个内联函数. The inline
说明符指示
实现函数体的内联替换
调用点优于通常的函数调用机制。
执行此内联替换不需要实现
在通话时;然而,即使这个内联替换是
省略,7.1.2定义的内联函数的其他规则应
仍然受到尊重。
这告诉我们inline
是向编译器发出内联代码吹出的请求,并且编译器不需要执行该替换。但这也告诉我们,无论编译器执行此内联替换,7.1.2 中定义的“其他规则”仍然适用。
很久以前,C++ 编译器采用的优化技术相对于今天的编译器来说还很原始。在那些日子里,使用它可能是有意义的inline
作为一种优化技术。但如今,编译器在优化代码方面做得更好了。如今,编译器应用的技术将使您的代码比内联更快,即使该函数实际上并未内联。 (一个可能的例子,RVO。)
因此,最终结果是,虽然 7.1.2/2 的最初意图可能是为程序员提供手动优化技术,但现代编译器的优化过于激进,以至于这一最初意图在很大程度上没有实际意义。
所以剩下的一切inline
那些“其他规则”。那么其他规则是什么? (C++11 的用语)
4/内联函数应在每个翻译单元中定义
它是 ODR 使用的并且应具有完全相同的定义
每种情况(3.2)。 [ 注意:对内联函数的调用可能是
在其定义出现在翻译单元中之前遇到的。 —
尾注]如果函数的定义出现在翻译中
在其第一个声明为内联之前的单元,该程序是
格式不正确。如果具有外部链接的函数被声明为内联
一个翻译单元,应在所有翻译中声明为内联
它出现的单位;无需诊断。内联
具有外部链接的函数在所有函数中应具有相同的地址
翻译单位。外部内联中的静态局部变量
函数总是引用同一个对象。中的字符串文字
外部内联函数的主体在不同的地方是同一个对象
翻译单位。 [ 注意:默认出现的字符串文字
参数不在内联函数体内仅仅因为
表达式用于该内联函数的函数调用中。 - 结尾
注意] 在外部内联函数体内定义的类型是
每个翻译单元中的类型相同。
让我们看一个例子。假设我们有这个类template
:
文件:foo.h
#ifndef FOO_H
#define FOO_H
#include <string>
#include <sstream>
class StringBuilder
{
public:
template <typename T> inline StringBuilder& operator<<(const T& t)
{
mStream << t;
return * this;
}
operator std::string () const;
private:
std::stringstream mStream;
};
StringBuilder::operator std::string() const
{
return mStream.str();
}
#endif
If we #include
这个文件在main.cpp
并使用StringBuilder
, 一切安好:
文件:main.cpp
#include <iostream>
#include <string>
int main()
{
double d = 3.14;
unsigned a = 42;
std::string s = StringBuilder()
<< "d=" << d << ", a=" << a;
std::cout << s << "\n";
}
Output:
jdibling@hurricane:~/dev/hacks$ ./hacks
d=3.14, a=42
但如果我们想使用StringBuilder
在第二个翻译单元中,我们会遇到一个问题:
文件:其他.cpp
#include <iostream>
#include <string>
#include "foo.h"
void DoSomethingElse()
{
unsigned long l = -12345;
long l2 = 223344;
std::string s = StringBuilder()
<< "l=" << l << ", l2=" << l2;
std::cout << s << "\n";
}
编译器输出:
ninja: Entering directory `.'
[1/3] Building CXX object CMakeFiles/hacks.dir/main.o
[2/3] Building CXX object CMakeFiles/hacks.dir/other.o
[3/3] Linking CXX executable hacks
FAILED: : && /usr/bin/g++ -Wall -std=c++11 -g CMakeFiles/hacks.dir/main.o CMakeFiles/hacks.dir/other.o -o hacks -rdynamic -lboost_regex-mt && :
CMakeFiles/hacks.dir/other.o: In function `std::operator|(std::_Ios_Openmode, std::_Ios_Openmode)':
/home/jdibling/dev/hacks/foo.h:21: multiple definition of `StringBuilder::operator std::string() const'
CMakeFiles/hacks.dir/main.o:/home/jdibling/dev/hacks/foo.h:21: first defined here
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.
StringBuilder::operator std::string()
被定义两次;曾在main.cpp
并再次在other.cpp
——这违反了单一定义规则。
我们可以通过创建函数来解决这个问题inline
:
class StringBuilder
{
public:
// [...]
inline operator std::string () const;
// ^^^^^^
private:
std::stringstream mStream;
};
编译器输出:
ninja: Entering directory `.'
[1/3] Building CXX object CMakeFiles/hacks.dir/main.o
[2/3] Building CXX object CMakeFiles/hacks.dir/other.o
[3/3] Linking CXX executable hacks
这有效,因为现在operator std::string
在两个翻译单元上的定义完全相同。它与直接在声明中定义函数具有相同的效果:
class StringBuilder
{
public:
operator std::string () const
{
return mStream.str();
}
private:
std::stringstream mStream;
};