C++的一些特性

最近在读一份C++项目,对C++有了很多新的认识。这里随意记录一下。内容多为转载整理

C++11 : 外部模板(Extern Template),显式的实例化与外部模板的声明

转自http://book.51cto.com/art/201306/400332.htm

外部模板的使用实际依赖于C++98中一个已有的特性,即显式实例化(Explicit Instantiation)。显式实例化的语法很简单,比如对于以下模板:

template <typename T> void fun(T) {} 

我们只需要声明:

template void fun<int>(int); 

这就可以使编译器在本编译单元中实例化出一个fun(int)版本的函数(这种做法也被称为强制实例化)。而在C++11标准中,又加入了外部模板(Extern Template)的声明。语法上,外部模板的声明跟显式的实例化差不多,只是多了一个关键字extern。对于上面的例子,我们可以通过:

extern template void fun<int>(int); 

这样的语法完成一个外部模板的声明。
那么回到一开始我们的例子,来修改一下我们的代码。首先,在test1.cpp做显式地实例化:

#include "test.h"  
template void fun<int>(int); // 显示地实例化  
void test1() { fun(3); } 

接下来,在test2.cpp中做外部模板的声明:

#include "test.h"  
extern template void fun<int>(int); // 外部模板的声明  
void test1() { fun(3); } 

这样一来,在test2.o中不会再生成fun(int)的实例代码。整个模板的实例化流程如图所示。
在这里插入图片描述
可以看到,由于test2.o不再包含fun(int)的实例,因此链接器的工作很轻松,基本跟外部变量的做法是一样的,即只需要保证让test1.cpp和test2.cpp共享一份代码位置即可。而同时,编译器也不用每次都产生一份fun(int)的代码,所以可以减少编译时间。这里也可以把外部模板声明放在头文件中,这样所有包含test.h的头文件就可以共享这个外部模板声明了。这一点跟使用外部变量声明是完全一致的。

在使用外部模板的时候,我们还需要注意以下问题:如果外部模板声明出现于某个编译单元中,那么与之对应的显示实例化必须出现于另一个编译单元中或者同一个编译单元的后续代码中;外部模板声明不能用于一个静态函数(即文件域函数),但可以用于类静态成员函数(这一点是显而易见的,因为静态函数没有外部链接属性,不可能在本编译单元之外出现)。

在实际上,C++11中“模板的显式实例化定义、外部模板声明和使用”好比“全局变量的定义、外部声明和使用”方式的再次应用。不过相比于外部变量声明,不使用外部模板声明并不会导致任何问题。如我们在本节开始讲到的,外部模板定义更应该算作一种针对编译器的编译时间及空间的优化手段。很多时候,由于程序员低估了模板实例化展开的开销,因此大量的模板使用会在代码中产生大量的冗余。这种冗余,有的时候已经使得编译器和链接器力不从心。但这并不意味着程序员需要为四五十行的代码写很多显式模板声明及外部模板声明。只有在项目比较大的情况下。我们才建议用户进行这样的优化。总的来说,就是在既不忽视模板实例化产生的编译及链接开销的同时,也不要过分担心模板展开的开销。

__attribute__((visibility(“default”)))

转自https://blog.csdn.net/mutourenzhang/article/details/47803803

-fvisibility=default|internal|hidden|protected
gcc的visibility是说,如果编译的时候用了这个属性,那么动态库的符号都是hidden的,除非强制声明。

创建一个c源文件,内容简单

#include<stdio.h>
#include<stdlib.h>

__attribute ((visibility("default"))) void not_hidden ()
{
printf("exported symbol/n");
}

void is_hidden ()
{
printf("hidden one/n");
}

想要做的是,第一个函数符号可以被导出,第二个被隐藏。
先编译成一个动态库,使用到属性-fvisibility
gcc -shared -o libvis.so -fvisibility=hidden vis.c

现在查看

# readelf -s libvis.so |grep hidden
7: 0000040c 20 FUNC GLOBALDEFAULT11 not_hidden
48: 00000420 20 FUNC LOCALHIDDEN 11 is_hidden
51: 0000040c 20 FUNC GLOBAL DEFAULT 11 not_hidden
可以看到,属性确实有作用了。

现在试图link
vi main.c

int main()
{
	not_hidden();
	is_hidden();
	return 0;
}

试图编译成一个可执行文件,链接到刚才生成的动态库,
gcc -o exe main.c -L ./ -lvis
结果提示:

/tmp/cckYTHcl.o: In function `main':
main.c:(.text+0x17): undefined reference to `is_hidden'

说明了hidden确实起到作用了。

__attribute__((visibility("default")))

试想这样的情景,程序调用某函数A,A函数存在于两个动态链接库liba.so,libb.so中,并且程序执行需要链接这两个库,此时程序调用的A函数到底是来自于a还是b呢?
这取决于链接时的顺序,比如先链接liba.so,这时候通过liba.so的导出符号表就可以找到函数A的定义,并加入到符号表中,链接libb.so的时候,符号表中已经存在函数A,就不会再更新符号表,所以调用的始终是liba.so中的A函数。
为了避免这种混乱,所以使用__attribute__((visibility(“default”)))attribute((visibility(“hideen”))) 设置这个属性。

DLL的import和exoprt

转自https://blog.csdn.net/dongfengsun/article/details/1477797
DLL的export是指将DLL中的函数和数据输出到其它程式中,以供其使用。DLL的import是指使用DLL的程式引入DLL中的函数和数据。

DLL的export

DLL中包含有一个表,称为export table(以下简称ET),其中包含了DLL中可以被外部程式使用的所有函数和数据的名字。只有记录在ET中的函数和数据才可以被外部程式所使用(如果没有.DEF文件的话),其它所有没有记录在ET中的函数和数据都被视为是DLL私有的。因此,要将DLL中的函数和数据export只有两个方法:

  • 为DLL创建一个.DEF文件(模块定义文件),并在build该DLL时使用这个.DEF文件。使用这种方法使你可以将函数按序号export。

  • 在DLL中想要export的函数和数据定义前添加_declspec(dllexport)关键字(对于函数和变量定义,加在最前面;对于class定义,加在class关键字后),这样该函数和数据就会被添加到ET中。使用这种方法函数将按名字export。

在WINDOWS下,无论使用上述的哪一种方法,都必须要将export函数声明为_stdcall。

关于C和C++的兼容问题

如果要写C和C++兼容的DLL,因为在C和C++下使用了不同的名字修饰规则以及不同的调用约定,所以,如果DLL是用C编写和编译的,则在用于C++模块时,函数的声明前应加上extern “C”关键字,以告诉LINKER使用C外部连接(即按照C名字修饰规则在外部模块中寻找函数);反之,如果DLL是用C++编写和编译的,则在用于C模块时,函数的声明前要加上extern “C++”关键字。VC++通过_cplusplus宏来标识C++程式。如果是C++程式,VC编译器就会为你定义_cplusplus宏。所以在DLL中可以使用如下的技术来解决兼容问题:

#ifdef _cplusplus
extern “C” {
#endif
// 将所有的函数声明放在这里
#ifdef _cplusplus
}
#endif
DLL的import

外部程式的一个源文件要使用DLL中的函数和数据,就像要使用外部模块中的函数和数据一样,必须首先给出函数和数据的声明;对于class则要给出类的定义。这就称为import。对于VC编译器,Import DLL的函数和数据的语法与一般的声明类似,但要在前面加上_declspec(dllimport)关键字(对于函数和变量声明,加在最前面;对于class定义,加在class关键字后)。如果是函数,则该关键字是可选的,但使用该关键字有可能会导致编译器产生较高效的代码。但对于变量和class,则必须使用该关键字。
通过使用以下的技术,可以编写在.LIB文件和外部程序源文件通用的头文件:

#ifdef _EXPORTING
	#define CLASS_DECLSPEC    __declspec(dllexport)
#else
	#define CLASS_DECLSPEC    __declspec(dllimport)
#endif

编译器提供的_EXPORTING宏可以用于标式该源文件来自DLL文件还是外部程式。

C++ explicit关键字详解

参考https://www.cnblogs.com/rednodel/p/9299251.html

=delete 特性

https://blog.csdn.net/lmb1612977696/article/details/80035487

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值