杂注__declspec

今天看到一个一个好玩的东西:__declspec。这是Microsoft提供的一个关键词,配合一些属性可以对标准c++做一些扩充。(所以只能在Visual Studio上用)
总结一下其中我觉得有意思的。如有错漏还望指摘~

__declspec的用法是:__declspec(属性1 [, 属性2, … ])。
其中我学会了的有如下一些属性:

  1. deprecated
    这个属性可以修饰变量、函数、结构体、命名空间等等,用来标明这个名称或者实体,由于效率、安全或者其他原因已经被弃用。如果使用你程序的人调用了这个名称或者实体,编译时就会报错(warning),必须使用#pragma warning来关闭该警告。这是出于兼容性考虑的,如果调用你程序的人由于某种原因暂时不能更改接口,那么保留原接口就可以使得他可以继续调用而不会出错。但是出于安全或者效率角度必须进行一定的提醒。
    使用方法:

    __descspec(deprecated) int test1;
    __descspec(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命名空间,那么上图的说明部分就会显示自定义的提示字符串。
    注意:

    1. 前四种使用方式都会出现4996警告,最后一种#pragma的方式则会出现4995警告;
    2. 如果使用第三种或者第四种,一定是两个中括号。这种使用方式是C++自带的属性,因此各个编译器都可以识别;
    3. 声明和定义任意一个被修饰为已弃置,那么这个名字或实体就会被声明为已弃置。

    如果仍然想要使用已被弃置的名称或者实体,就需要使用:

    #pragma warning(disable: 4996)
    

    来关闭这些警告。(如果是最后一种,则需要关掉4995警告)

  2. noretern
    这个属性我看了半天,官方解释是:

    指示函数不返回。
    此属性仅应用到函数声明中正在声明的函数名。若拥有此属性的函数实际上返回,则行为未定义。
    若函数的任何声明指定此属性,则其首个声明必须指定它。若函数在一个翻译单元中声明为带 [[noreturn]] 属性,而同一函数在另一翻译单元中声明为不带 [[noreturn]] 属性,则程序非良构;不要求诊断。

    我:???感觉自己就是个菜鸡.jpg。为什么每个字都认识,连在一起就不知道在说什么???

    直到我看到一个评论:
    这个属性就是标明那些一定会把程序干掉的函数。23333333

    使用方法:

    __declspec(noreturn) void end(int x){
        exit(x);
    }
    [[noreturn]] void end2(int x){ exit(x); }
    

    注意:正如官方解释所说,如果一个函数被声明了noreturn属性,那么在整个程序中对这个函数的第一次声明中必须指定noreturn属性。

  3. 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指向的函数,并将每一个括号里的值依次传入函数。
    值得注意的是

    1. 这里是在调用时就进行了转换。也就是说,如上面的代码,如果将getN函数变成private的,那么将会调用失败。只有当函数和属性同时是public的时候,外界才能正常调用。
    2. 可以重复定义,但是以第一次定义为准,之后的定义不会报错,但是无效。
    3. 函数可以重载,但是因为是将函数伪装成了变量,而变量只有固定的类型,所以要求所有的重载函数返回值必须相同,与设定的虚拟变量的类型一致。

    再举一个简单的例子来说明这个属性的使用:

    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;

  4. novtable
    这个属性是用来修饰类或者结构。作用是,禁止构造虚函数指针表。因此当类或结构中含有虚函数时,将禁止类或结构的实例化。是用来指定接口类的。
    使用方法:

    __declspec(novtable) class Interface{
        virtual void func1(int x);
        virtual void func2(double y);
    }
    

    此时Interface类只能作为接口类,不能被实例化。其中的虚函数也相当于纯虚函数。

  5. noinline
    这个属性用来指定函数为非内联函数。用于类内声明的函数。我们知道,如果函数在类内定义,一般会默认为内联函数,这时用这个属性可以取消其内联属性。如:

    class test{
    public:
        __declspec(noinline) void print(int x){
            cout << x << endl;
        }
    }
    

    此时print函数虽然在类内进行了定义,也足够简单,但强制其为非内联函数。

  6. dllimportdllexport

  7. naked
    这个属性是用来修饰函数的,标明编译的时候不再生成用来现场保护的prolog和epilog,用于想在程序内用内联汇编自定义prolog和epilog的情况。值得注意的是:

    1. 这个属性只在ARM和x86下生效,在x64下不提供该属性
    2. 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
    	}
    }
    
  8. allocate
    allocate属性修饰变量,指定在编译成汇编语言时,变量存储在什么字段。
    使用方法:

    #pragma section("mycode",read,write)
    __declspec(allocate("mycode")) int x = 0;
    
  9. selectany
    我们知道,在头文件中定义全局变量会引发错误,这是由于如果多次引用头文件,就会导致全局变量的多次定义。而selectany属性则可以避免这种错误,告诉编译器挑一个就行。
    使用方法:

    //test.h
    __declspec(selectany) int x = 0;
    
    //test2.h
    #include "test.h"
    
    //main.cpp
    #include "test.h"
    #include "test2.h"
    
    //这里不用selectany属性就会报错
    
原创文章 34 获赞 41 访问量 5961

猜你喜欢

转载自blog.csdn.net/qq_44844115/article/details/100556920
今日推荐