c++17的inline、static和inline static变量和函数

Elegracer 于 2020-12-17 发布

前言

继上篇文章后,我很自然地会考虑到inlinestatic之间的区别,以及它们组合起来的效果。

像上一篇文章所说,在 c++17 中inline可以修饰变量,那么如果不考虑 c++17 之前是什么样,inlinestatic对变量和函数都有什么作用呢?

从 c++17 开始,inline这个关键字的意思从「建议编译器将函数内连」变成了「允许多个定义」,并且从只用于函数,扩展到了可以对变量进行修饰。

在下面的讨论中

  1. 函数和变量都默认在.h头文件中,若在.cpp中会特别说明。
  2. 所述成员变量,默认为类的成员变量。

inline, static, inline static 变量

对于变量而言,我们从 3 种情况来考虑:局部变量、全局变量、成员变量。

1. 局部变量

inline不能修饰局部变量。

static可以修饰局部变量。作用就是使得本应「在局部块尾部被销毁」的变量,变成「从程序开始运行就分配内存,在第一次运行到定义处被初始化,并在程序结束时被销毁」。

inline static因为有inline,也不能修饰局部变量。

int main() {
    // inline int a = 1; // compile error
    static int b = 2;
    // inline static int c = 3; // compile error
}

2. 全局变量

inline可以修饰全局变量。如同上一篇文章所说,可以使得所有包含其所在.h文件的编译单元都可以访问到同一个变量,且共享同一块内存。也就是说,它此时就是一个单例。

static可以修饰全局变量。与inline不同的是,所有包含其所在.h文件的编译单元,会单独有一块内存存储这个变量,即彼此完全独立。

inline static对全局变量的效果与static一样。

// core.h
inline int a = 1;
static int b = 2;
inline static int c = 3; // same as `static int c = 3;`

3. 成员变量

inline不能单独修饰成员变量。

static可以修饰成员变量。它使得这个变量脱离具体实例的控制,而依附于类本身,有一块独立于实例的内存空间。对于struct A { static int a_int; } a;,可以通过A::a_inta.a_int两种方式访问到里面的静态成员变量。值得注意的是,对于static修饰的成员变量,需要对其在单独的.cpp文件里用int A::a_int;来定义它,否则struct A就是一个不完整的类型。同时,不能够直接在类的声明中对其赋初值。

inline static可以修饰成员变量。它的作用和static单独修饰差不多。区别在于,static成员变量不要求类的声明时,该成员是一个完整类型,而inline static成员变量要求该成员是一个完整类型。但方便地是,不要求在单独.cpp里定义。

// A.h
struct A {
    // inline int a; // compile error
    static int b;
    // static int c = 3; // compile error
    inline static int d = 4;
};

// A.cpp
int A::b = 2;

inline, static, inline static 函数

对于函数而言,函数中无法直接定义函数,而是往往通过 lambda 表达式、局部类、std::function 对象等来实现的,所以我们只考虑剩下的 2 种情况:全局函数、成员函数。

1. 全局函数

inline可以修饰全局函数。和inline修饰的变量一样,可以使得所有包含其所在.h文件的编译单元都可以访问到同一个函数,且共享同一块内存。也就是说,可以直接在.h文件里把函数定义好,能够被任意数量的.cpp包含,也不用担心函数重复定义的编译错误。

static可以修饰全局函数。与inline不同的是,所有包含其所在.h文件的编译单元,会单独有一块内存存储这个函数的定义,即彼此完全独立。

inline static对全局变量的效果与static一样。

单看上去好像static全局函数在实际使用的时候和inline没有区别,然而一旦这个函数包含static变量,就可以体现出差别了。

由于inline全局函数共享一个定义,所以里面的static变量也是共享的,在一个地方调用这个函数并对这个static变量进行了修改,就会在下一次另一个地方调用这个函数的时候,受到其影响。而static全局函数并不会这样,彼此是完全独立的。

// core.h

inline int return_int() {
    static int a = 5;
    return ++a;
}

static int return_another_int() {
    static int a = 5;
    return ++a;
}

inline static int return_yet_another_int() {
    static int a = 5;
    return ++a;
}

void invoke_inline_function();
void invoke_static_function();
void invoke_inline_static_function();
// core.cpp

#include "core.h"
#include <iostream>

void invoke_inline_function() {
    std::cout << "return_int(): " << return_int() << std::endl;
}

void invoke_static_function() {
    std::cout << "return_another_int(): " << return_another_int() << std::endl;
}

void invoke_inline_static_function() {
    std::cout << "return_yet_another_int(): " << return_yet_another_int() << std::endl;
}
// main.cpp

#include "core.h"
#include <iostream>

int main() {
    std::cout << "main: return_int(): " << return_int() << std::endl;

    std::cout << "main: return_another_int(): " << return_another_int() << std::endl;

    std::cout << "main: return_yet_another_int(): " << return_yet_another_int() << std::endl;

    invoke_inline_function();
    invoke_static_function();
    invoke_inline_static_function();

    return 0;
}

输出将会是

$ ./a.out
main: return_int(): 6
main: return_another_int(): 6
main: return_yet_another_int(): 6
return_int(): 7
return_another_int(): 6
return_yet_another_int(): 6

2. 成员函数

对于成员函数来说,比较清晰的理解方式是,将inlinestatic看成独立的修饰符。

首先上面说过,从 c++17 开始,inline的意义就从「建议编译器将函数内连」变成了「允许多个定义」。

然后对于成员函数而言,如果在类的定义中,只给出声明的成员函数默认non-inline的,如果直接给出了完整的定义,则默认是inline的。

对于成员函数而言,inline的作用其实主要是规定了函数实现应处的位置。对于non-inline成员函数来说,其实现必须放在另一个.cpp文件里,否则当多个编译单元包含这个实现并且链接在一起时,链接器会报错。而inline成员函数的定义允许直接放在同一个.h文件里。

static成员变量的作用仅仅是让这个函数独立于类的实例,而依附于类本身存在。

// A.h
struct A {
    inline static int how(); // #1
    static int can();        // #2
    inline int it();         // #3
};

inline int A::how() {        // #4
    return 10;
}

inline int A::it() {         // #5
    return 12;
}

// A.cpp

#include "A.h"

int A::can() {               // #6
    return 11;
}
// main.cpp

#include "A.h"
#include <iostream>

int main() {
    std::cout << A::how() << std::endl;
    std::cout << A::can() << std::endl;
    std::cout << A().it() << std::endl;
    return 0;
}

值得注意的是,#1#4中,只需要一处加上inline即可;#3#5也是。但#2#6中的static必须且只能放在#2中。

参考资料