C++:类内static成员

C++静态成员详解
本文深入探讨了C++中静态成员的概念与应用,包括不同访问权限下的静态成员变量及函数的使用方法,以及静态常量成员的定义和注意事项。

static成员是与类相关的,而不是与类的各个对象保持关联。

实际上,这在一定程度上减少了资源开销。

想象类内某const int size,每个对象的值都应当是相同的,如果单纯的将其定义为const成员,那么每一个对象都会有一个size的副本,浪费资源。

由于类内static是整个类的所有对象共享,所以它没有this指针

由于是类内static,所以static也会有不同的访问权限。

public static

static data and static function

这种用法并不常见,看下面的例子:

`#include <iostream>
using namespace std;
class Test
{

public:
    static int x;
    static void fun(){
        cout << "Calling public static function fun()" << endl;
    }
};

int Test::x = 10;//类内声明,类外定义

int main()
{
    Test test;

    cout << Test::x << endl;
    cout << test.x << endl;
    test.fun();
    Test::fun();
    return 0;
}

其中x和fun()是static成员,访问权限是public。这意味着该类的对象从类外即可轻松访问x和fun()。
注意这里强调,static function 是没有this指针的,所以test.fun()实际上只是通过test调用了Test类的fun(),和test并无关系。
test.x同理。
可见这种调用方式可能会产生歧义,因此更建议使用ClassName::static data的调用方式。

private static

private static是最为常用的方式。
因为访问权限是static,所以有很好的封装性。对于类内经常用到的操作,可以将其抽象成一个工具函数(tool function)方便使用
,因为这个tool function是属于整个类的,因此将其编写为static更能节省空间。

#include <iostream>
using namespace std;

class Test
{
private:
    static int maxSize;
    static void toolFunction();//do something in the class
public:
    static void accessMaxSize(); 
};
int main()
{
    Test test;

    return 0;
}

maxSize是private data,那么以前的test.maxSizeTest::masSize这两种用法都不能访问maxSize
对于这种数据想要完成访问操作,需要定义一个public static method,正如类中的static void accessMaxSize()

static const 成员

这里是比较容易弄混的地方,看下面这个例子。

#include <iostream>
using namespace std;

class Test
{
private:
    static const int maxSize = 5;
    int array[maxSize];
public:
    Test(){
    cout << &maxSize << endl;
    }

};

int main()
{
    Test test;

    return 0;
}

Test类中声明了一个static const int maxSize,并且赋予初值,作为成员array[]的长度参数,直到这里一切都OK,但是
你会发现,上述代码无法通过编译。

编译器提示
**undefined reference toTest::maxSize’|**`

使用未定义的成员?
等等?我们不是给予了初值了吗?
实际上,如果去掉上述代码的构造函数部分的cout << &maxSize << endl;语句编译器就会开心的放你通过。

这很奇怪,为什么对maxSize取地址编译器就报错呢?
这意味着编译器没有给maxSize分配空间,也就是说,maxSize未定义!

Effective C++ 是这样解释的:

我们看到的static const int maxSize = 5是一个声明式而非定义式。c++要求对任何一个东西提供定义式,但是如果是class专属常量且为static且为integral(ints,chars,bools),则需特殊处理,只要不取它的地址,编译器一切都ok。

最好的解决办法是,在类外提供一个定义式

const int Test::maxSize;

需要注意的是,不要const int Test::maxSize = someValue,即便这个someValue是5也不可以,编译器会提示

error: duplicate initialization of 'Test::maxSize'|

触发重定义错误。

<think>我们正在处理一个关于在Windows DLL中导出静态成员变量的问题。根据引用内容,特别是引用[1]和引用[4],我们知道在DLL中导出一个时,可能会遇到静态成员变量在外部使用的问题。引用[1]提到,使用dll的工程在编译时只会编译头文件,而不会编译dll的cpp文件,因此在外部使用静态成员变量时会出现未定义的错误。解决方法是使用`__declspec(dllimport)`来告诉外部工程去lib文件中找到静态变量的定义。 引用[4]展示了一个例子,其中在DLL中定义了一个`CMath`,并在其中声明了一个静态成员变量`PI`,然后在外部工程中使用了这个静态变量。但是,如果我们在DLL的cpp文件中初始化静态成员变量,并且这个静态成员变量需要被导出,则可能会遇到“无法定义dllexport实体”的错误。 根据引用[2],我们知道在C++17之后,我们可以使用`inline static`成员变量来避免在外定义静态成员变量。但是,如果我们需要在DLL中导出这个静态成员变量,并且支持C++17之前的编译器,则需要另一种方法。 解决方案通常如下: 1. 在DLL的头文件中,使用条件编译来指定导出或导入。通常定义一个宏,例如: #ifdef MYLIBRARY_EXPORTS #define MYLIBRARY_API __declspec(dllexport) #else #define MYLIBRARY_API __declspec(dllimport) #endif 2. 对于的静态成员变量,我们不能直接在定义中用`__declspec(dllexport)`修饰静态成员变量。正确的做法是: - 在头文件中声明静态成员变量,并在外使用`MYLIBRARY_API`导出该变量(即在外定义时导出)。 - 注意:在DLL的源文件中定义该静态成员变量时,必须加上导出符号(即使用`MYLIBRARY_API`),而在外部使用该DLL时,头文件中的声明会被预处理为`__declspec(dllimport)`。 但是,在外定义静态成员变量时,如果我们在定义时使用了`__declspec(dllexport)`,则这个定义会被正确导出。然而,错误“无法定义dllexport实体”通常是因为我们试图在同一个定义中同时指定存储(如`static`)和`dllexport`,或者定义的位置不正确。 正确的写法: 头文件(例如Test.h): ```cpp #ifdef MYLIBRARY_EXPORTS #define MYLIBRARY_API __declspec(dllexport) #else #define MYLIBRARY_API __declspec(dllimport) #endif class MYLIBRARY_API CMath // 导出整个 { public: static double PI; // 声明静态成员变量 }; ``` 然后在源文件(Test.cpp)中定义静态成员变量: ```cpp #define MYLIBRARY_EXPORTS #include "Test.h" double CMath::PI = 3.14159; // 不需要再次使用MYLIBRARY_API,因为整个已被导出 ``` 但是,如果只想导出静态成员变量而不是整个,可以这样: 头文件: ```cpp #ifdef MYLIBRARY_EXPORTS #define MYLIBRARY_API __declspec(dllexport) #else #define MYLIBRARY_API __declspec(dllimport) #endif class CMath // 不导出整个 { public: static MYLIBRARY_API double PI; // 导出静态成员变量 }; ``` 然后在源文件中: ```cpp #define MYLIBRARY_EXPORTS #include "Test.h" // 在外定义并初始化,由于头文件中已经声明为MYLIBRARY_API,这里不再需要(也不能)重复 double CMath::PI = 3.14159; ``` 注意:在第二种方式中,我们只导出了静态成员变量,而没有导出整个。这样,在DLL外使用该静态成员变量时,通过`__declspec(dllimport)`就可以正确导入。 如果遇到“无法定义dllexport实体”的错误,可能是因为在定义静态成员变量时又加上了`__declspec(dllexport)`,这是不允许的。因为头文件中已经声明了该变量为`__declspec(dllexport)`(当编译DLL时,MYLIBRARY_EXPORTS被定义,所以头文件中的`PI`会变为`__declspec(dllexport)`),在定义时只需要写出型和名字即可,不能再重复修饰。 另外,引用[2]提到C++17引入的`inline static`成员变量可以避免外定义。如果我们使用C++17或更高版本,并且只希望静态成员变量在DLL内部使用,或者不需要导出整个,那么可以这样做: ```cpp class CMath { public: inline static double PI = 3.14159; // 在内初始化 }; ``` 但是,如果我们需要在DLL中导出这个静态成员变量,那么仍然需要按照上述方法导出,因为`inline static`并不处理跨DLL边界的问题。 总结解决办法: 1. 确保在头文件中,静态成员变量的声明使用条件编译宏(如`MYLIBRARY_API`)修饰。 2. 在DLL的源文件中定义静态成员变量时,不要再使用`__declspec(dllexport)`,因为头文件中的声明已经处理了导出(通过定义宏`MYLIBRARY_EXPORTS`)。 3. 在DLL的工程中定义`MYLIBRARY_EXPORTS`宏(通常在项目设置中定义,或在包含头文件之前定义),这样头文件中的静态成员变量声明会被展开为`__declspec(dllexport)`。 4. 在使用DLL的工程中,不要定义`MYLIBRARY_EXPORTS`,这样头文件中的静态成员变量声明会被展开为`__declspec(dllimport)`,从而正确导入。 按照上述步骤,应该能够解决静态成员变量在cpp文件中初始化时出现的“无法定义dllexport实体”错误。 </think>### static成员变量在cpp文件赋初值时显示无法定义dllexport实体的解决方案 根据引用内容,您遇到的问题是在DLL中导出的静态成员变量时,在cpp文件初始化时遇到的编译错误。核心解决方案如下: #### 正确做法(分步实现) 1. **头文件声明**: ```cpp // MyClass.h #ifdef MYDLL_EXPORTS #define MYDLL_API __declspec(dllexport) #else #define MYDLL_API __declspec(dllimport) #endif class MYDLL_API MyClass { public: static double PI; // 声明静态成员变量 // 其他成员... }; ``` 2. **cpp文件初始化**: ```cpp // MyClass.cpp #define MYDLL_EXPORTS #include "MyClass.h" // 初始化静态成员(不添加任何修饰符) double MyClass::PI = 3.14159; // ✅ 正确写法 ``` #### 常见错误原因及修复 1. **错误写法示例**: ```cpp // ❌ 错误:重复添加导出修饰符 __declspec(dllexport) double MyClass::PI = 3.14; ``` **错误原因**:头文件已通过`class MYDLL_API`导出整个,cpp文件中无需也不能重复添加`dllexport`[^1][^3] 2. **解决方案**: - 确保cpp文件中**仅进行变量定义**,不添加`__declspec(dllexport)` - 检查预处理器定义:在DLL项目中需定义`MYDLL_EXPORTS`宏(通常在项目属性 > C/C++ > 预处理器中添加) #### 其他注意事项 1. **C++17优化方案**(如引用[2]所述): ```cpp // 使用inline static(仅限C++17+) class MYDLL_API MyClass { public: inline static double PI = 3.14159; // 声明+初始化一步完成 }; ``` > 此方法无需在cpp文件中单独定义[^2] 2. **跨DLL边界使用**: ```cpp // 使用DLL的客户端代码 #include "MyClass.h" int main() { std::cout << MyClass::PI; // 正确导入 } ``` > 需链接生成的`.lib`文件(#pragma comment(lib)或项目设置)[^4] #### 完整工作流程 1. DLL工程: - 预处理器定义 `MYDLL_EXPORTS` - 头文件声明 `class MYDLL_API MyClass` - cpp文件初始化 `double MyClass::PI = ...`(无修饰符) 2. 客户端工程: - **不定义** `MYDLL_EXPORTS` - 包含头文件自动转为`dllimport` - 链接DLL对应的`.lib`文件 > 此方案确保静态成员变量的存储空间在DLL中分配,客户端通过lib文件正确解析引用[^1][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值