作者:Jon Lee
链接:https://www.zhihu.com/question/53082910/answer/133612920
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

inline 绝对是C++里最让人混淆的关键词之一了(比static还过分)。

============== Update 30 Nov 2016

看其他评论里有提到static 的。个人评价一下 static + inline 一起:那就是把死人往活里搞,活人往死里搞的赶脚,坑之深简直不忍直视。先上追加的3个结论;后面有代码,有耐心的小伙伴们拿回去自己试。

3. 谨慎使用 static:如果只是想把函数定义写在头文件中,用 inline,不要用static。static 和 inline 不一样:

  • static 的函数是 internal linkage。不同编译单元可以有同名的static 函数,但该函数只对 对应的编译单元 可见。如果同一定义的 static 函数,被不同编译单元调用,每个编译单元有自己单独的一份拷贝,且此拷贝只对 对应的编译单元 可见。
  • inline 的函数是 external linkage,如果被不同编译单元调用,每个编译单元引用/链接的是同一函数,同一定义。
  • 上面的不同直接导致:如果函数内有 static 变量,对inline 函数,此变量对不同编译单元是共享的(Meyer's Singleton);对于static 函数,此变量不是共享的。看后面的代码就明白区别了。

4. static inline 函数,跟 static 函数单独没有差别,所以没有意义,只会混淆视听。

5. inline 函数的定义不一定要跟声明放在一个头文件里面:定义可以放在一个单独的头文件 .hxx 中,里面需要给函数定义前加上 inline 关键字,原因看下面第 2.点;然后声明 放在另一个头文件 .hh 中,此文件include 上一个 .hxx。这种用法 boost里很常见:优点1. 实现跟API 分离,encapsulation。优点2. 可以解决 有关inline 函数的 循环调用问题:这个不展开说了,看一个这个文章就懂了:Headers and Includes: Why and How 第 7 章,function inlining。

Reference: inline specifier

============== 原答案30 Nov 2016

1. 不要再把 inline 和编译器优化挂上关系了,太误导人。编译器不傻,inline is barely a request。你不加inline,小函数在开O3时,编译器也会自动给你优化了。看到inline时,应该首先想到其他用意,在考虑编译器优化。

2. inline最大的用处是:非template 函数,成员或非成员,把定义放在头文件中,定义前不加inline ,如果头文件被多个translation unit(cpp文件)引用,ODR会报错multiple definition。

============== static / inline 代码

a.hh

#ifndef A_HH
# define A_HH # include <iostream> namespace static_test
{
static int& static_value() // (!*!) Or change this to inline
{
static int value = -1;
return value;
} namespace A
{
void set_value(int val);
void print_value();
}
} #endif

  

a. cc

# include "a.hh"

namespace static_test
{
namespace A
{
void set_value(int val)
{
auto& value = static_value();
value = val;
} void print_value()
{
std::cout << static_value() << '\n';
}
}
}

  

b.hh:

#ifndef B_HH
# define B_HH # include <iostream> namespace static_test
{
namespace B
{
void set_value(int val);
void print_value();
};
} #endif

  

b.cc:

# include "a.hh"
# include "b.hh" namespace static_test
{
namespace B
{
void set_value(int val)
{
auto& value = static_value();
value = val;
} void print_value()
{
std::cout << static_value() << '\n';
}
}
}

main. cc

# include "a.hh"
# include "b.hh" int main()
{
static_test::A::set_value(42); static_test::A::print_value();
static_test::B::print_value(); static_test::B::set_value(37); static_test::A::print_value();
static_test::B::print_value(); return 0;
}
  • a.hh 中标注 (!*!) 的那行,如果是inline,输出:42,42,37,37。value 在整个程序中是个Singleton
  • 如果是 static,输出:42,-1,42,37。value 在不同编译单元是不同的拷贝,即使它被标注 static

c++ inline使函数实现可以在头文件中,避免多重定义错误的更多相关文章

  1. [C/C++]在头文件中使用static定义变量意味着什么

    文章出处:http://www.cnblogs.com/zplutor/ 看到有一位同学在头文件中这么写: static const wchar_t* g_str1 = - static const ...

  2. 将inline、template声明和定义在头文件中

    如果要在要在源文件(a.cpp)中内联的展开一个函数(fun),则该源文件(a.cpp)中必须包含此函数(fun)的定义.如果要在多个文件中内联的展开fun,则在所有的源文件中都必须包含fun的定义. ...

  3. C++编译错误 --- 成员函数定义在 .h 文件中出现重定义错误(Error LNK 2005)

    今天写了一个简单的类,定义在 .h 文件中, 类很简单就将其成员函数定义在了一起(class类后面).运行的时候出现了如下图所示的编译错误(error LNK2005) 查资料,大部分都是说需要加上 ...

  4. C++-模板的声明和实现为何要放在头文件中

    源: http://blog.csdn.net/lqk1985/archive/2008/10/24/3136364.aspx 如何组织编写模板程序 发表日期: 1/21/2003 12:28:58 ...

  5. C ++模板的声明和实现为何要放在头文件中?

    源: http://blog.csdn.net/lqk1985/archive/2008/10/24/3136364.aspx 如何组织编写模板程序 发表日期: 1/21/2003 12:28:58 ...

  6. cctype头文件中的一些内容

    1. string 标准库 1.1初始化 string s1; 默认构造函数s1为空 string s2(s1); 将s2初始化为s1的一个副本 string s3(“value”); 将s3初始化为 ...

  7. C++内联函数、函数模板之于头文件

    一.基本说明 C++标准中提到,一个编译单元是指一个.cpp文件以及它所include的所有.h文件,.h文件里的代码将会被扩展到包含它的.cpp文件里,然后编译器编译该.cpp文件为一个.obj文件 ...

  8. c语言头文件中定义全局变量的问题

    c语言头文件中定义全局变量的问题 (转http://www.cnblogs.com/Sorean/) 先说一下,全局变量只能定义在 函数里面,任意函数,其他函数在使用的时候用extern声明.千万不要 ...

  9. 不包含SDK头文件, 补全API定义

    /// @file main.cpp /// @brief 不包含SDK头文件, 补全API定义 #ifdef __cplusplus extern "C" { #endif /* ...

随机推荐

  1. Sersync + Rsync 代码分发

    简介: Sersync 是基于 inotify 来编写的 Linux 系统文件监控工具,当监控到文件发生变化时,调用 rsync 同步文件. 类似的功能,以前有用 rsync + inotify 实现 ...

  2. think in avalon

    1.不要设计,也不要通过DOM操作去改变你的页面 你用jQuery去设计一个页面,并让它动起来.这是因为jQuery就是让一切简单的事情变复杂的罪魁祸首. 但是用avalon,你必须从零开始去构思你的 ...

  3. 从零玩转JavaWeb系列7web服务器-----get与post的区别

    总结get与post的区别 get参数通过url传递,post放在request body中. get请求在url中传递的参数是有长度限制的,而post没有. get比post更不安全,因为参数直接暴 ...

  4. Fedora下安装Scrapy遇到的两个问题

    error:/usr/lib/rpm/redhat/redhat-hardened-cc1: No such file or directory 需要安装redhat-rpm-config sudo ...

  5. go_数组

    go语言中数组是值类型 [10]int 和 [20]int 是不同类型,不能用作参数传递 调用func f(arr [10]int)会拷贝数组 go语言一般不用数组用切片slice package m ...

  6. 【算法】2-sat问题【模板】

    什么是2-sat问题 有n个布尔型变量xi,另外m个需要满足的条件.每个条件都是“xi为真/假或者xj为真/假”.这句话中的“或者”意味着两个条件中至少有一个正确.2-sat问题的目标是给每个变量赋值 ...

  7. Ajax工作原理和原生JS的ajax封装

    前言: 之所以用ajax作为博客的开篇,是因为无论从ajax的出现还是从它的作用上来说,ajax对于前端无疑是意义重大的.甚至可以说,是ajax带来了前端这个行业.当然,历史并不能说明当下,曾经的辉煌 ...

  8. Spring中Aspect的切入点的表达式定义细节

    用过很多次切面aspect了,对于表达式总是记得很模糊,今天总结一下. 1.切面做如下设置则只会拦截返回值为String类型的方法 @Aspect public class MyInterceptor ...

  9. Bash空格的那点事-乾颐堂CCIE

    先了解下bash中什么时候该用空格,什么时候不该用. 1. 等号赋值两边不能有空格 2. 命令与选项之间需要空格 3. 管道两边空格可有可无 我们来看看常见的问题 1. 赋值时等号两边或者只有左边多了 ...

  10. Linux useradd 与 adduser的区别, /sbin/nologin 与 /bin/bash

    摘自:https://blog.csdn.net/danson_yang/article/details/65629948 Linux useradd 与 adduser的区别, /sbin/nolo ...