`
tjmzgn
  • 浏览: 155886 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

C++ 内联inline函数

    博客分类:
  • C++
阅读更多
内联inline函数



在C中保持效率的一种方法是使用宏,他的行为类似于函数调用但却没有调用的开销(like a function call without the normal function call overhead.)。

宏是由由预处理器preprocessor而非编译器compiler处理的,其直接替换宏代码,没有参数入栈、函数调用及返回等开销。



但是在C++中使用宏有两个问题:

宏类似于函数调用但并非总是如此,其有副作用;

预处理器不能访问类的成员变量(preprocessor has no permission to access class member data),因此不能作为成员函数。

为了保持预处理器宏的效率同时增添真正的函数的安全及类范围的特性,C++中采用了内联函数。



预处理器的陷阱

如果你认为编译器和预处理器的行为相似,那么你则进入了陷阱,如:



#define FLOOR(x,b) x>=b?0:1

当调用形式如下时,

if(FLOOR(a&0x0f,0x07)) // ...

宏将扩展为

if(a&0x0f>=0x07?0:1)

由于&的优先级最低,因此其行为和预想的不一样了



因此在使用宏时,要将参数加上(),以防止改变了表达式的优先顺序

#define FLOOR(x,b) ((x)>=(b)?0:1)



但即使如此,宏仍然有其副作用,如

#define BAND(x) (((x)>5 && (x)<10) ? (x) : 0)

//: C09:MacroSideEffects.cpp

#include "../require.h"

#include <fstream>

using namespace std;

#define BAND(x) (((x)>5 && (x)<10) ? (x) : 0)

int main() {

ofstream out("macro.out");

assure(out, "macro.out");

for(int i = 4; i < 11; i++) {

int a = i;

out << "a = " << a << endl << '\t';

out << "BAND(++a)=" << BAND(++a) << endl;

out << "\t a = " << a << endl;

}

} ///:~

a = 4

BAND(++a)=0

a = 5

a = 5

BAND(++a)=8

a = 8

a = 6

BAND(++a)=9

a = 9

a = 7

BAND(++a)=10

a = 10

a = 8

BAND(++a)=0

a = 10

a = 9

BAND(++a)=0

a = 11

a = 10

BAND(++a)=0

a = 12



根据a的大小,++a的执行次数不一样,因此系统的副作用始终在变,即函数的功能特性不稳定,这是程序员所不希望的,其本质原因在于宏是简单的文本替换。



宏和访问权限

谨慎的使用宏可以避免上述问题,但是仍然有一个不可逾越的障碍是,宏没有对于成员访问范围的概念。

class X {

int i;

public:

。。。

}

#define VAL(X::i) // Error

宏不能访问类的私有成员,另外不能确定你访问的是哪个对象。因此很多程序员为了性能将某些成员变量属性更改为public的,但这样就失去了private的安全性能。



内联函数

为了解决宏对于类的私有成员变量的访问权限问题,将宏的概念纳入编译器的控制范围即可,这就是内联函数,他具备函数的一切特性,但是没有函数调用的开销,因为内联函数象预处理宏一样在调用处被扩展。



类内部定义的函数自动扩展为内联函数,但是你也可以通过inline关键字声明某函数为内联函数。但是声明时必须和函数定义放在一起,否则编译器将其视为普通函数。对于内联函数,编译器会进行参数和返回值的类型检查。

inline int plusOne(int x);这样是不起任何作用的,必须如下方式声明

inline int plusOne(int x)

{ return ++x; }



必须将内联函数的定义放在头文件中,编译器将函数类型和函数体放在其符号表中,当遇到相应的调用时,就将其替换;头文件中的内联函数有个特殊状态,每个文件中都有内联函数的实现,但并不会出现重复定义的错误,因为内联函数是在编译阶段替换的,并没有链接过程,不涉及导函数分配的内存地址等问题。



内联函数和编译器

为了理解内联函数何时有效,需要了解编译器如何处理内联。

编译器将函数类型包括函数名、参数个数及其类型还有返回值类型保存在符号表中,当函数体的语法无误时将其实现也保存在符号表中,代码的形式取决于编译器。当遇到调用内联函数时,编译器会分析参数和返回值类型并可能做适当的强制转换,都没有问题时就会进行代码替换,并可能还有进一步的优化。



什么时候不能使用内联

内联函数有两种限制,当不能使用内联时,编译器将之视为普通函数,为其分配内存空间,通常会出现多重定义的错误,但是链接器被告知忽略这种问题。



当函数的功能过于复杂时,编译器不会实施内联,这取决于编译器,但通常情况下,循环或者过多的代码不会被内联,因为此时代码执行的时间可能比函数调用的时间多很多,内联失去了意义。



另外一种情况是需要显式或隐式的得到某函数的地址时,编译器要产生地址则必须为其分配内存空间;而进行内联替换时只是将其保存在符号表中,并不为其分配空间。



总之,inline关键词只是对编译器的一种建议,并非强制,是否内联取决于编译器的分析。



内联中的前向引用

当在内联函数中调用了类中还未声明的函数怎么办呢?

这种情况,编译器仍然可以将其内联,因为语法规则表明只有到类声明的“}”处才进行内联函数的替换。

//: C09:EvaluationOrder.cpp

// Inline evaluation order

class Forward {

int i;

public:

Forward() : i(0) {}

// Call to undeclared function:

int f() const { return g() + 1; }

int g() const { return i; }

};

int main() {

Forward frwd;

frwd.f();

} ///:~



构造和析构函数中的隐藏活动

在构造和析构函数中你可能误认为内联比实际的效率高,因为构造和析构过程中可能含有隐含活动,如当类中含有子对象时必须调用子对象的构造和析构函数。这种子对象可能是成员函数也可能是继承而来的。成员对象的例子如下:



// Hidden activities in inlines

#include <iostream>

using namespace std;

class Member {

int i, j, k;

public:

Member(int x = 0) : i(x), j(x), k(x) {}

~Member() { cout << "~Member" << endl; }

};

class WithMembers {

Member q, r, s; // Have constructors

int i;

public:

WithMembers(int ii) : i(ii) {} // Trivial?

~WithMembers() {

cout << "~WithMembers" << endl;

}

};

int main() {

WithMembers wm(1);

} ///:~



减少clutter

在实际的工程项目中,若在类中定义函数,则会弄乱类的接口并使得类很难使用,因此有些人认为任何成员函数的定义应该放在类的外部实现,以保持类的整洁。如果需要优化的话,则用inline关键字。



//: C09:Noinsitu.cpp

// Removing in situ functions

class Rectangle {

int width, height;

public:

Rectangle(int w = 0, int h = 0);

int getWidth() const;

void setWidth(int w);

int getHeight() const;

void setHeight(int h);

};

inline Rectangle::Rectangle(int w, int h)

: width(w), height(h) {}

inline int Rectangle::getWidth() const {

return width;

}

inline void Rectangle::setWidth(int w) {

width = w;

}

inline int Rectangle::getHeight() const {

return height;

}

inline void Rectangle::setHeight(int h) {

height = h;

}

int main() {

Rectangle r(19, 47);

// Transpose width & height:

int iHeight = r.getHeight();

r.setHeight(r.getWidth());

r.setWidth(iHeight);

} ///:~



Inline成员函数应该放在头文件中,而非inline函数应该放在定义文件中。

使用inline关键字的形式声明还有一个好处是使得各个成员函数的定义具备统一的风格。



更多的预处理器特性

当需要用到预处理器中的三种特性时,采用宏而非内联函数。

分别为字符串化(stringizing),字符串连接(string concatenation),及符合粘贴(token pasting)。字符串化即强制将x转化为字符数组,通过#实现。当两个字符串之间没有任何符号时,字符串连接将使其合并为一个字符串。这两个特性在编写调试代码时特别有用,如:

#define DEBUG(x) cout << #x " = " << x << endl



可以用此技术来跟踪代码的执行,在执行代码的同时打印相应信息。

#define TRACE(s) cerr << #s << endl; s

这种方法在只有单一语句的for循环中可能会出现问题,如

for(int i = 0; i < 100; i++)

TRACE(f(i));

此时可以将“;”更改为“,”成为逗号表达式。

#define TRACE(s) cerr << #s << endl, s

或者更改为do while结构,如:

#define TRACE(s) do{ cerr << #s << endl; s ;} while(0)



符号粘贴

符号粘贴即将两个符号粘贴在一起生成一个新的符号,通过“##”实现。

#define FIELD(a) char* a##_string; int a##_size

class Record {

FIELD(one);

FIELD(two);

FIELD(three);

// ...

};

上述方式生成一个标识符作为字符串,另一个作为串的长度,不仅便于阅读,同时消除了编码出错的可能性,并且便于维护。

在Linux的内核代码中存在大量这样数据的定义,尤其是些init初始化阶段的数据,或者是某些保存在特殊段的数据结构
分享到:
评论

相关推荐

    C++内联汇编示例

    工程主要时C++内联汇编的示例,内部简单的用汇编实现了函数传参及调用,循环以及if语句的实现

    C++ 实验1 重载函数和内联函数的应用 

    1、掌握重载函数概念及用法 ...如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。

    c++内联函数(inline)使用详解

    主要介绍了c++内联函数(inline)使用详解,需要的朋友可以参考下

    c++中的内联函数inline用法实例

    如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上 inline,否则就认为不是内联的。内联函数的inline要加在函数前面,不可以加在声明前面。 class A { public:void Foo(int x, int y) { } // ...

    内联函数-14.08.25

    对 C++ 内联函数进行了说明

    C++中inline函数详解

    本文主要记录了C++中的inline函数,也就是内联函数,主要记录了以下几个问题: 一、C++为什么引入inline函数? 主要目的:用它代替C语言中表达式形式的宏定义来解决程序中函数调用的效率问题。 C语言中的宏定义:#...

    详解C++中的内联函数和函数重载

    这种在函数调用处直接嵌入函数体的函数称为内联函数(inline function),又称内嵌函数或内嵌函数。 指定内联函数的方法很简单,只需要在定义函数时增加 inline 关键字。 注意:是在函数定义时增加 inline 关键字,...

    C++编程中队内联函数的理解和使用

    主要介绍了C++编程中队内联函数的理解和使用,简单举例讲解了inline关键字引出的内联函数的相关知识,需要的朋友可以参考下

    C++ 关键字 inline详细介绍

    1. 内联函数 在C++中我们通常定义以下函数来求两个整数的最大值: 代码如下:int max(int a, int b){ return a &gt; b ? a : b;} 为这么一个小的操作定义一个函数的好处有: ① 阅读和理解函数 max 的调用,要比读一...

    C++类的内联成员函数应放在哪

    复习C++ Primer的时候,看到了关于C++类的内联成员函数的放置,应该放在头文件中。那么这到底是为什么呢?仅仅是一种代码规范问题还是必须这样做呢?  下面我来讲讲我自己的理解吧。要彻底理解这个问题,首先要...

    c++中inline的用法分析

    与非inline函数不同的是,inline函数必须在调用该函数的每个文本文件中定义。当然,对于同一程序的不同文件,如果inline函数出现的话,其定义必须相同。对于由两个文件compute.C和draw.C构成的程序来说,程序员不能...

    详解C++中的inline用法

    在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数。 栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。 在系统下,栈空间是有限...

    inline hook内联汇编dll之屏蔽记事本粘贴功能

    inline hook内联汇编dll之屏蔽记事本粘贴功能,HOOKAPI函数SetClipboardData源代码,编译环境是VS05版

    vscode-inline-parameters:Visual Studio Code的扩展,在调用函数时添加内联参数注释

    VSCode的内联参数 Visual Studio Code的扩展,在调用函数时添加内联参数注释。 这是所的功能,可以在阅读代码时为您提供更多的上下文,从而通过内联显示参数名称更容易理解不同的功能参数所指的是什么。 您再也不必...

    探讨C++中不能声明为虚函数的有哪些函数

    内联成员函数;构造函数;友元函数。 1.为什么C++不支持普通函数为虚函数? 普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。 多态的运行期...

    52_虚函数可以声明为inline吗1

    class Derived :public Base {//从语法上讲,这可以写成inline,只是当基类指针调派类时,不能内联,编译器会动忽略掉inline/

    深入C++中inline关键字的使用

    一、在C&C++中 一、inline 关键字用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达式形式的宏定义。 表达式形式的宏定义一例: #define ExpressionName(Var1,Var2) ((Var1)+(Var2))*((Var1)-(Var2))为...

Global site tag (gtag.js) - Google Analytics