今天看到一个一个好玩的东西:__declspec。这是Microsoft提供的一个关键词,配合一些属性可以对标准c++做一些扩充。(所以只能在Visual Studio上用)
总结一下其中我觉得有意思的。如有错漏还望指摘~
__declspec的用法是:__declspec(属性1 [, 属性2, … ])。
其中我学会了的有如下一些属性:
-
deprecated
这个属性可以修饰变量、函数、结构体、命名空间等等,用来标明这个名称或者实体,由于效率、安全或者其他原因已经被弃用。如果使用你程序的人调用了这个名称或者实体,编译时就会报错(warning),必须使用#pragma warning来关闭该警告。这是出于兼容性考虑的,如果调用你程序的人由于某种原因暂时不能更改接口,那么保留原接口就可以使得他可以继续调用而不会出错。但是出于安全或者效率角度必须进行一定的提醒。
使用方法:__declspec(deprecated) int test1; __declspec(deprecated("This function is deprecated for some reason!")) void test2(void); struct [[deprecated]] test3; namespace [[deprecated("This name space is deprecated for some reason!")]] test4; #pragma deprecated(test2);
当调用已弃置的名称或实体时,就会出现这样的警告:
如果自定义了提示字符串,如上面的test2函数和test4命名空间,那么上图的说明部分就会显示自定义的提示字符串。
注意:- 前四种使用方式都会出现4996警告,最后一种#pragma的方式则会出现4995警告;
- 如果使用第三种或者第四种,一定是两个中括号。这种使用方式是C++自带的属性,因此各个编译器都可以识别;
- 声明和定义任意一个被修饰为已弃置,那么这个名字或实体就会被声明为已弃置。
如果仍然想要使用已被弃置的名称或者实体,就需要使用:
#pragma warning(disable: 4996)
来关闭这些警告。(如果是最后一种,则需要关掉4995警告)
-
noretern
这个属性我看了半天,官方解释是:指示函数不返回。
此属性仅应用到函数声明中正在声明的函数名。若拥有此属性的函数实际上返回,则行为未定义。
若函数的任何声明指定此属性,则其首个声明必须指定它。若函数在一个翻译单元中声明为带 [[noreturn]] 属性,而同一函数在另一翻译单元中声明为不带 [[noreturn]] 属性,则程序非良构;不要求诊断。我:???感觉自己就是个菜鸡.jpg。为什么每个字都认识,连在一起就不知道在说什么???
直到我看到一个评论:
这个属性就是标明那些一定会把程序干掉的函数。23333333使用方法:
__declspec(noreturn) void end(int x){ exit(x); } [[noreturn]] void end2(int x){ exit(x); }
注意:正如官方解释所说,如果一个函数被声明了noreturn属性,那么在整个程序中对这个函数的第一次声明中必须指定noreturn属性。
-
property
这个属性非常有趣,他可以把函数伪装成变量。
这个属性的名字就叫做属性。所谓属性,就是类或者结构体里,表面上看上去像一个成员变量的东西。
先来介绍使用方法:__declspec(property(get = getFunc, put = setFunc)) type x[]; //这里可以是x,x[], x[][], ...
然后再来说明,什么叫把函数伪装成变量呢?比如如下这样一个计算Fibonacci数列的非常简单的类:
class Fibonacci{ public: int getN(int x){ if(x <= 0) return -1; if(x == 1 || x == 2) return 1; int i = 1, j = 1, tmp; for(int l = 2; l < x; l++){ tmp = i + j; i = j; j = tmp; } return j; } __declspec(property(get = getN)) int f[]; } int main(){ Fibonacci x; cout << x.f[6] << endl; //8 }
当调用Fibonacci中的f[6]时,输出了8。就好像类里真的有这样一个数组一样。然而实际上却是根据输入进行计算得出来的。这就是将函数伪装成一个变量。
这个属性构造了一个虚拟的变量,然而实际上却将这个变量指向了两个函数。当这个变量为左值时,调用put指向的函数,并将每一个括号里的值以及右值依次作为参数传入函数;当变量为右值时,就调用get指向的函数,并将每一个括号里的值依次传入函数。
值得注意的是:- 这里是在调用时就进行了转换。也就是说,如上面的代码,如果将getN函数变成private的,那么将会调用失败。只有当函数和属性同时是public的时候,外界才能正常调用。
- 可以重复定义,但是以第一次定义为准,之后的定义不会报错,但是无效。
- 函数可以重载,但是因为是将函数伪装成了变量,而变量只有固定的类型,所以要求所有的重载函数返回值必须相同,与设定的虚拟变量的类型一致。
再举一个简单的例子来说明这个属性的使用:
class test{ private: unordered_map<int, int> map; public: int getM(int key){ auto it = map.find(key); if(it == map.end()) return -1; else return it->second; } void insertM(int key, int value){ map[key] = value; } __declspec(property(get = getM, put = insertM)) int x[]; }
这里就将字典map伪装成了数组x;
-
novtable
这个属性是用来修饰类或者结构。作用是,禁止构造虚函数指针表。因此当类或结构中含有虚函数时,将禁止类或结构的实例化。是用来指定接口类的。
使用方法:__declspec(novtable) class Interface{ virtual void func1(int x); virtual void func2(double y); }
此时Interface类只能作为接口类,不能被实例化。其中的虚函数也相当于纯虚函数。
-
noinline
这个属性用来指定函数为非内联函数。用于类内声明的函数。我们知道,如果函数在类内定义,一般会默认为内联函数,这时用这个属性可以取消其内联属性。如:class test{ public: __declspec(noinline) void print(int x){ cout << x << endl; } }
此时print函数虽然在类内进行了定义,也足够简单,但强制其为非内联函数。
-
dllimport和dllexport
-
naked
这个属性是用来修饰函数的,标明编译的时候不再生成用来现场保护的prolog和epilog,用于想在程序内用内联汇编自定义prolog和epilog的情况。值得注意的是:- 这个属性只在ARM和x86下生效,在x64下不提供该属性。
- naked修饰的函数不能是内联函数。
使用方式:
__declspec(naked) void func1(const char* str) { __asm { /* prolog */ push ebp mov ebp, esp sub esp, __LOCAL_SIZE } printf("%s\n", str); __asm { /* epilog */ mov esp, ebp pop ebp ret } }
-
allocate
allocate属性修饰变量,指定在编译成汇编语言时,变量存储在什么字段。
使用方法:#pragma section("mycode",read,write) __declspec(allocate("mycode")) int x = 0;
-
selectany
我们知道,在头文件中定义全局变量会引发错误,这是由于如果多次引用头文件,就会导致全局变量的多次定义。而selectany属性则可以避免这种错误,告诉编译器挑一个就行。
使用方法://test.h __declspec(selectany) int x = 0; //test2.h #include "test.h" //main.cpp #include "test.h" #include "test2.h" //这里不用selectany属性就会报错