318 - Inline Static Variable
🧑💻 User
In this lecture, we’re going to learn about inline static member variables, and these are static membervariables you can set up and initialize them in the class definition itself.If you remember in the last lecture, we weren’t able to do something like this, and if we did that,we did get a compiler error.If we marked the member variable as inline, we are telling the compiler to allow this syntax here tocompile.And what it is going to do is to constrain this member variable to the translation unit where this classis going to show up and that’s going to avoid problems and that’s going to be avoiding one definitionrule issues.And this is going to work.We can’t really say much about how this is going to work under the hood.We’re going to have more details to understand that starting the next chapter.But for now, note that if you want to initialize your static member variable inside the class, youcan mark it as inline and it is going to work.Now that you know this, we’re going to head over to Visual Studio code and play with this a littlemore.Okay.So here we are in our working folder.The current project is in line static member variables.We’re going to grab the files from the last project because we have a good starting point from there.So we’re going to go there and grab everything except for the rooster file because we’re going to bebuilding our own in this lecture and we’re going to paste them in place and we’re going to open thisup in Visual Studio code pretty quick.Okay, We’re going to open up our main CPP file and the point files, and I don’t think we need thesome other translation file anymore so we can ditch it and really take this out.And let’s see, let’s see what we can do.We can delete that.We don’t need this anymore.Let’s remove this and we are going to go in our class and mark our static member variable as inlineand we’re going to be allowed to initialize that like this.The syntax is going to work now because this guy is in line and the compiler knows that this is notgoing to be problematic.If we go in the main function, we can use our program like we have been using it in the last lectureand it is going to work.We’re going to see a one and a four printed out because that’s what we got in the last lecture.And the point count was basically how many points we have created in our program.At this point, we have one, which is this guy here.So we print a one, we’re going to add three points that we store in this array.And if we print here, we’re going to get four points.So let’s build this with GCC.We’re going to do that.The world is good.We can clear and run Rooster.We should see a one and a four.And this is working exactly as we expect.This is really all I wanted you to see in this lecture that you can use inline static member variablesif you want to initialize your thanks in the class declaration itself.And another thing I should point out is that this was only possible starting in C plus plus 17.So if you are on C plus plus 14 or 11 or any other C plus plus version, this is not going to work,but we are using C plus plus 20 here.So this is working.So this is really all I had to share in this lecture.I hope you found it interesting.We are going to stop here in this one.In the next one, we’re going to learn about static constants.Go ahead and finish up here and meet me there.
🤖 Assistant
你好!很高兴能帮助你学习C++。作为一个C++开发者和你的老师,我将用最清晰、最详细的方式来讲解“inline static 成员变量”这个概念。
C++ 进阶:深入理解 inline static
成员变量
课程摘要: 本课程将详细讲解C++17引入的 inline static
成员变量。你将学会如何在类定义中直接初始化静态成员变量,以及 inline
关键字在此处的特殊作用,从而避免传统静态成员初始化中的“一次定义规则” [One Definition Rule (ODR)] 问题。
1. 详细讲解 (Detailed Explanation)
在C++中,static
关键字有很多用途,其中之一就是修饰类的成员变量。我们首先来回顾一下 static
成员变量,然后引入 inline static
的概念。
1.1 static
成员变量回顾
定义: 当一个类的成员变量被声明为
static
时,它就不再属于类的某个具体对象,而是属于整个类本身。这意味着:- 共享性: 所有的类对象共享同一个
static
成员变量的副本。无论你创建多少个类的对象,它们都访问和修改的是同一个static
成员变量。 - 生命周期:
static
成员变量的生命周期与程序的生命周期相同。它在程序启动时被创建,在程序结束时被销毁,而不是在对象创建时创建,对象销毁时销毁。 - 访问方式: 你可以通过类名直接访问
static
成员变量(例如ClassName::static_variable
),也可以通过对象访问,但通常推荐通过类名访问,因为它更清晰地表明了变量的类级别属性。
- 共享性: 所有的类对象共享同一个
传统初始化方式: 在C++17之前(以及对于非
const static
整型成员变量),static
成员变量通常不能在类定义内部直接初始化。你需要在类定义之外,在一个源文件
[.cpp file] 中进行定义和初始化。- 为什么不能在类内部直接初始化? 这涉及到C++的“一次定义规则” [One Definition Rule (ODR)]。如果一个
static
成员变量在头文件中被声明,并在头文件中直接初始化,那么当这个头文件被多个源文件
[source files] 包含时,这个static
成员变量就会在每个源文件
对应的翻译单元
[translation unit] 中都被定义一次。链接器在尝试将这些翻译单元
组合成一个可执行文件时,就会发现同一个变量被定义了多次,从而导致链接错误。
举例说明传统方式:
cpp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// Point.h
class Point {
public:
Point();
// 声明一个静态成员变量
static int point_count;
};
// Point.cpp
// 在源文件中定义并初始化静态成员变量
int Point::point_count = 0; // 必须在这里初始化
Point::Point() {
point_count++;
}这种方式确保了
point_count
在整个程序中只有一个定义。- 为什么不能在类内部直接初始化? 这涉及到C++的“一次定义规则” [One Definition Rule (ODR)]。如果一个
1.2 “一次定义规则” (One Definition Rule, ODR)
“一次定义规则” [One Definition Rule (ODR)] 是C++编程中一个非常重要的概念。它规定了在整个程序中,每个非内联 [non-inline] 的函数、每个变量、每个类型等都只能有唯一的定义。如果同一个实体被定义了多次,就会导致链接错误。对于 static
成员变量而言,传统的做法就是将其定义和初始化放在一个 .cpp
文件中,以确保它只被定义一次。
1.3 inline static
成员变量的引入 (C++17)
从C++17标准开始,C++引入了一个非常方便的特性:允许在类定义内部直接初始化 static
成员变量,只需要给它加上 inline
关键字。
语法:
cpp1
2
3
4
5
6
7// ClassName.h
class ClassName {
public:
// ...
inline static int static_member_variable = initial_value;
// ...
};它解决了什么问题? 它的主要目的是为了方便,特别是对于那些需要在头文件中定义和初始化的
static
成员变量,比如在header-only
库中。通过inline static
,你不再需要额外的.cpp
文件来定义和初始化这些静态成员。
1.4 inline
关键字的作用
你可能已经知道,inline
关键字可以用于函数,作为对编译器的“建议”,希望编译器将函数体直接展开到调用点,以减少函数调用的开销。但当 inline
用于 static
成员变量时,它的作用完全不同,它主要与“一次定义规则” [One Definition Rule (ODR)] 相关:
- 允许在多个翻译单元中定义: 当一个
static
成员变量被标记为inline
时,编译器就知道,即使这个变量在多个翻译单元
[translation units](即多个.cpp
文件编译后的目标文件)中都被定义和初始化了,它也是合法的。 - 链接器处理: 链接器在最终链接时会确保所有这些定义都指向同一个实际的变量实例,从而避免了链接错误。它就像编译器和链接器之间的一个特殊约定:
inline
的static
成员变量允许在多个地方出现定义,但最终只会被“挑选”一个出来使用。
总结一下 inline static
的核心:
它允许你在 头文件
[header file] 中的类定义内部声明和初始化 static
成员变量,同时避免了传统 static
成员变量可能导致的 ODR
问题。这使得代码更简洁,特别是对于小型项目或 header-only
库非常有用。
1.5 C++ 版本兼容性
非常重要的一点: inline static
成员变量是在 C++17 标准中引入的。这意味着如果你使用C++11、C++14或更早的标准,这个特性将不可用,编译器会报错。在学习或使用时,请确保你的编译器支持C++17或更高版本(例如C++20)。本课程中,我们假设你使用的是C++17或C++20。
2. 代码示例 (Code Example)
让我们根据课程内容,通过一个具体的代码示例来理解 inline static
成员变量的使用。
目标: 我们将创建一个 Point
类,并使用 inline static
成员变量 point_count
来追踪创建了多少个 Point
对象。
首先,设置你的项目。假设你有一个名为 inline_static_member_variables
的工作文件夹。
文件结构:
1 | inline_static_member_variables/ |
Point.h (类的定义)
1 |
|
main.cpp (程序入口)
1 |
|
编译和运行:
- 保存文件: 将上述代码分别保存为
Point.h
和main.cpp
。 - 打开终端/命令提示符: 导航到你保存文件的目录。
- 编译 (使用 g++ 或 clang++):
为了确保使用C++17或更高标准,请添加-std=c++17
或-std=c++20
标志。bash1
g++ main.cpp -o my_program -std=c++17 -Wall -Wextra
g++
: C++ 编译器(如果你在Windows上,可能需要安装 MinGW 或 MSYS2;在Linux/macOS上通常预装)。main.cpp
: 要编译的源文件。-o my_program
: 指定输出可执行文件的名称为my_program
(Windows上是my_program.exe
)。-std=c++17
: 启用C++17标准。-Wall -Wextra
: 启用所有常用警告和额外警告,帮助你写出更健壮的代码。
- 运行:预期输出:bash
1
./my_program
这个输出与课程中提到的预期输出Code1
2
3
4
5
6
7
8------------------------------------------
欢迎来到 inline static 成员变量学习!
------------------------------------------
创建第一个Point对象后,当前 Point 数量: 1
再创建3个Point对象后,当前 Point 数量: 4
------------------------------------------
示例完成!
------------------------------------------1
和4
完全一致。
3. 问答闪卡 (QA Flash Cards)
Q1: inline static
成员变量和普通的 static
成员变量在声明和初始化上有什么不同?
A1: 普通的 static
成员变量通常在类定义内部声明,但在类定义外部的一个源文件中定义并初始化(因为要遵守ODR)。而 inline static
成员变量允许在类定义内部直接声明并初始化,并且可以在多个翻译单元中出现,链接器会处理好 ODR 问题。
Q2: 为什么C++17之前不能在类定义内部直接初始化 static
成员变量(除了 const static
整型)?
A2: 主要是因为“一次定义规则” [One Definition Rule (ODR)]。如果头文件中的 static
成员变量在类定义内部被初始化,当该头文件被多个源文件包含时,这个变量就会被定义多次,导致链接错误。
Q3: inline
关键字用于 static
成员变量时,其主要作用是什么?
A3: inline
关键字在这里并不意味着强制内联代码,而是告诉编译器和链接器,允许这个 static
成员变量在多个 翻译单元
[translation units] 中被定义。链接器在最终链接时会确保所有这些定义都指向同一个实际的变量实例,从而避免了 ODR 问题。
Q4: 从哪个C++标准版本开始,inline static
成员变量才被允许使用?
A4: 从 C++17 标准开始。
4. 常见误解与错误 (Common Misunderstandings and Mistakes)
- 误解
inline
的含义: 最常见的误解是认为inline static
成员变量的inline
关键字意味着像内联函数一样,会提升性能或将变量值直接“内联”到使用它的地方。实际上,这里的inline
与性能无关,它是一个与“一次定义规则” [One Definition Rule (ODR)] 相关的语言规则,允许变量在多个翻译单元中被定义。 - C++版本不兼容: 尝试在C++14或更早版本的项目中编译使用
inline static
成员变量的代码,会导致编译错误。新手可能不清楚这个特性是C++17才引入的。 - 忘记
static
关键字: 错误地将inline
关键字用于非static
成员变量。inline
只能用于static
成员变量(或函数),对于普通的成员变量,它没有意义。 - 误以为所有
static
成员变量都可以这样初始化: 只有inline static
成员变量才可以在类内部直接初始化。传统的非inline static
成员变量(特别是如果你的C++版本低于C++17或者需要旧的行为)仍然需要在类外定义和初始化。 - 过度使用或滥用: 尽管
inline static
很方便,但并不是所有static
成员变量都适合在头文件中初始化。例如,如果静态成员变量的初始化涉及复杂逻辑、依赖其他库或在多个.cpp
文件中有不同的用途(虽然这种情况不常见且通常是设计问题),则传统的在.cpp
文件中初始化可能更合适。但在大多数简单计数器或常量场景下,inline static
是非常棒的。
5. 编程练习 (Coding Exercise)
现在,请你尝试完成下面的编程练习。你需要修改现有的代码,使其能够追踪并显示程序中所有 Dog
对象的数量。
任务:
在 Dog
类中添加一个 inline static
成员变量 dog_count
,用于在每次创建 Dog
对象时增加计数。在 main
函数中,分别在创建第一个 Dog
对象后和创建更多 Dog
对象后,打印当前的 dog_count
值。
Dog.h
:
1 |
|
main.cpp
:
1 |
|
请你将代码填写完整,然后告诉我你的解决方案。