C++经验(十一)-- (inlining)内联函数的使用

news/2024/5/20 2:01:31 标签: c++, 开发语言, inline, 内联函数

不管是我们以前使用面向过程还是面向对象编程,编写代码过程中使用函数,能够降低代码的重复率,特别是在一些重复性比较高的代码中,也能够用降低运行程序的体积。但不可避免的也会带来一些程序运行上的时间消耗。

这个主要的时间消耗在这儿。函数执行的时候,首先要在栈上为形参和局部变量申请内存,同时也要将实参的值复制给形参,最后还需要将函数的返回地址写入栈,以便函数结束运行后,程序能够知道应该回到哪儿继续执行。最后才跳转到函数内部进行执行。

函数中 return 语句执行之后,前面我们在栈上为形参和局部变量申请的内存需要回收,然后从栈中取出写入的函数返回地址,记性执行。

上面的这整个过程是比较耗时的。

如果函数的逻辑比较复杂,上面的这个时间消耗可以忽略不计。但如果函数只有简单的几行代码,甚至不涉及到比较复杂逻辑运算的时候,上面的整个时间消耗在变量的对比下就会显得比较高了。

如果你的代码中有很多个地方都调了这个函数呢?

那函数调用的时间消耗可不是一笔小的开销。在C++中,有一种函数被叫做内联函数。函数如下,在C++中,一般是在函数声明的时候在函数前面加关键字 inline

inline int max(int a, int b)
{
    return a < b ? b : a;
}

如果是在类中,函数的如果在类定义的时候进行定义,那么,加不加关键字 inline 并没有什么异样,因为,这样的函数都会被默认为是内联函数

class A
{
public:
    int max(int a, int b)
    {
        return a < b ? b : a;
    }
}

如果函数的定义实在类的定义之外,那么就需要在函数声明的时候或者定义的时候在其前面加上关键字 inline

class A
{
public:
    inline int max(int a, int b);
}

int A::max(int a, int b)
{
    return a < b ? b : a;
}

他们是函数,动作也像函数,相对于宏来说有不可超越的好处。可以调用他们又不需要管函数调用所带来的额外开销。内联函数不仅仅是避免了在函数调用上的开销,同时编译器的最优化机制也会对不含有函数的代码进行优化。

但是需要注意的一点,编译器对内联函数的调用都是在对应的函数调用的地方进行函数体的替换,这也就是说,如果我们代码中大量的使用了或者定义了内联函数,可能并不会减少代码量,相反地可能还会增加代码量。代码的膨胀也会带来额外的换页行为,这会有效率上的损失。

template<typename _Tp, typename _Compare>
_GLIBCXX14_CONSTEXPR
inline const _Tp&
max(const _Tp& __a, const _Tp& __b, _Compare __comp)
{
    if (__comp(__a, __b))
        return __b;
    return __a;
}

上面的这个函数我是摘自stl算法头文件的,我们可以发现,这个函数被定义在头文件中,是一个内联函数,同时也是一个模版函数。 他们为什么通常被定义在头文件中呢?

其实前面我们已经说过了,内联函数一般是由编译器在编译阶段进行了代码段的拷贝和复制。那也就说,在编译阶段,编译器需要知道函数长什么样子。而 template 一旦被使用,编译阶段,进行具现化的时候也必须要知道模版的类型。并且 template 的具化是跟 inline 无关的。

大部分的编译器会拒绝将一些逻辑比较复杂的函数进行内联化。现在大多数的编译器都会在编译的时候生成一条警告信息。所以有些函数,就算函数体很小,编译器也会拒绝 inline ,比如虚函数。因为虚函数是在运行的时候才知道要执行的具体代码。

所以一个表面上看起来像是 inline 的函数并不一定是内联函数,这个主要取决于编译器。

再看下下面这个例子:


inline void func(){...}
void (*pf)() = func;

func();
pf();

上面的这两个函数调用,第一个在编译器阶段会被 inline,第二个不会,因为inline的过程是需要找到具体的函数进行函数体的替换,而编译器并不知道函数指针指向的具体函数。

同样的,有时候,看起来是肯定会被inline的函数也不一定会被 inline 的。比如下面这个:

class A
{
public:
    A(){};

private:
    std::string m_name{ "" };
    std::string m_addr{ "" };
}

class B : public A
{
public:
    B(){};

private:
    std::string m_school{ "" };
    std::string m_company{ "" };
}

上面类 B 的构造函数是在类的定义式中被定义的,它会被隐式的 inline 吗?

不会的,虽然表面上看着 类 B 的构造函数没有函数体,是非常适合被 inline 的一类函数,但实际上并不是这样的。C++做了一些保证,来确保,当对象被创建和销毁的时候进行一些必要的操作,比如,派生类构造的时候,基类也会被构造,同时包括派生类及其基类的成员也会被构造。对象析构的时候恰好相反。

所以上面的 类 B 的构造函数被执行的时候,会有一些隐形的代码被调用,而这些代码是编译器帮我们默认生成的,并不会显现的让我们看到。

B::B()
{
    A::A()
    {
        try{ m_name.std::string::string(); }
        catch{A::~A();}
        ....
    }

    try{ m_company.std::string::string(); }
    catch
    {
        m_name.std::string::~string();
        m_addr.std::string::~string();
        A::~A();
        m_school.std::string::~string();
        B::~B();
    }
}

是不是,看似一个简单的并且没有什么函数体的函数,其实在经过编译器之后,会被编译器生成很多的代码。

除了上述的这些,内联函数还有一个问题就是,如果改变了内联函数的函数体,那么所有调用该内联函数的文件都需要被重新构建,因为内联是进行了代码的替换。

总结一下:

  • 将那些函数体较小,计算逻辑并不复杂的函数声明为 inline 函数。
  • 函数模版的定义跟内联没有关系,因此不要因为在头文件中定义了template函数而将其声明为 inline
  • 表面上看起来可以内联的函数,并不一定一定会被 inline,因此在申明内联函数的时候一定要注意。
  • 通过函数指针调用的函数一定不会被编译器inlineing。

80% 的 运行时间是花费在 20% 的代码上的,因此,我们需要的是找到那20% 的代码并进行优化。


http://www.niftyadmin.cn/n/800720.html

相关文章

英语----定语从句----练习实战

// 固定搭配 The place which i often pay a visit was built in the 17th century. Last night I had a dream in which i became a Nobel Prize winner. The frightening thing is the rate at which it is increasing. Ace asked the police with whom he worked…

chart.js x轴显示不全_Tech 学堂 | 画面中的X、Y、Z (上)

说起轴线&#xff0c;或许大家最耳熟能详的词语就是我们在上一期 Woo Tech 中提及的“越轴”不过今天我们想跟大家聊的并非对话场景中的关系轴线而是画面中无时无刻不存在着的X轴、Y轴、Z轴不同于油画、照片等静态艺术作品&#xff0c;电影画面的空间是一种三维的动态空间。想…

Python赋值列表

Python赋值列表1.0 错误示范2.0 正确操作1.0 错误示范 # Python赋值列表 a [1,2,3,4,5] ba print("修改前&#xff1a;") print("a",a) print("b",b) a[2]99 print("修改后&#xff1a;") print("a",a) print("b&quo…

Qt自定义DateTime控件--实现日历及时间选择器自定义

以前写过一篇Qt通过重写 QScrollBar 实现一个滚动屏的数字显示控件&#xff0c;片面了的用了这个东西实现了一个时间滚动选择器。 后来又尝试了结合Qt自带的 QCanlendarWidget 实现了一个简单的DateTime控件&#xff0c;这个控件可以用&#xff0c;不过就是存在一些比较麻烦的…

图片3d轮放查看效果

本功能比較简单&#xff0c;就是一个大幕。左右滚动播放图片。 关键点在于怎样实现平滑的滚动&#xff0c;包含动画效果&#xff0c;3d效果等。<style>img {position: absolute;top:200;left:400px; /* border: 1px solid #333;*/padding: 2px 5px 2px 5px;-webkit-trans…

taro 如何使用dom_基于Taro开发的快应用体积优化方案

快应用重复打包问题使用Taro开发快应用,有一个问题绝对不能忽视,那就是体积问题.因为快应用打包的特性(1080以下)多个页面里,如果重复应用了一个第三方库,那么这个库的代码会一起打到这个页面中,导致一样的代码会存在多个页面中.因为目前1080版本的快应用没有全面铺开.如果你贸…

Qt自定义SearchLine控件--实现按钮及相应功能

最近迷上了自定义一些简单的控件&#xff0c;今天看一个搜索框的自定义。实现的功能是&#xff1a; 在 QLineEdit 的末尾添加一个 button &#xff0c;这个button 就是我们常见的搜索按钮点击按钮后可模拟发送 QLineEdit 的 textEdited 信号这个搜索框可以存储一些数据 效果如…

Eureka心跳机制与自动保护机制原理分析

Eureka心跳机制: 在应用启动后&#xff0c;节点们将会向Eureka Server发送心跳,默认周期为30秒&#xff0c;如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳&#xff0c;Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。 Eureka自动保护机制&am…