P a r t I . Part\ I. P a r t I .
Static Variables in a Function
可以认为,声明在函数中被直接初始化的静态变量,就相当于把变量声明在全局,但是静态变量的作用域仅仅在函数内部其作用。
Static Member Variables in a Class
在类里面声明的静态变量分配空间时只被分配一次,所以类中所有实例化的对象中的静态变量相同,也就是静态变量和所有对象共享。也正是因为这个原因,静态变量不能使用构造函数进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> using namespace std;class GfG {public : static int i; GfG (){ }; };int main () { cout << GfG::i; }
1 2 3 /tmp/ccPusLB5.o: In function `main': Solution.cpp:(.text.startup+0xd): undefined reference to `GfG::i` collect2: error: ld returned 1 exit status
编译器在编译阶段为可能存在的 int GfG::
打上引用,因为编译器假设 GfG::i
的定义会在其他地方提供,然而链接的阶段并没有找到为 GfG::i
分配空间的地方,所以抛出一个引用错误 undefined reference to GfG::i
Static Member Function in a Class
类似的,静态成员函数只能调用静态变量,被所有类共享。
全局静态变量具有内部链接,也就是对于链接器来说,全局静态变量是看不见的,他只能被当前的翻译单元所访问,可以用来防止其他相同名字的其他文件变量冲突。注意定义在头文件中的静态变量作用域也被限制在当前的翻译单元,也就是对所有引用头文件的翻译单元来说,每个翻译单元都有一个静态变量的副本
Static 修饰的变量或者函数,导致的性质就是他的作用域会被改变,要想理解 static 关键词,应该先对程序的编译过程有一定的认识。
参考链接
P a r t I I . Part\ II. P a r t II .
T a g D i s p a t c h Tag\ Dispatch T a g D i s p a t c h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> using namespace std;struct t1 {};struct t2 {};void fun (int a, t1) { cout << "Calling function of tag t" << a << endl; }void fun (int a, t2) { cout << "Function with tag t" << a << endl; }int main () { fun (1 , t1{}); fun (2 , t2{}); return 0 ; }
标签调度,其实是利用函数重载的一种 c++ 技巧,可以用来处理这样一种情况:你要做出不同的操作对具有相似参数和返回值的同名函数。
这是一种静态多态(s t a t i c p o l y m o r p h i s m static\ polymorphism s t a t i c p o l y m or p hi s m )
参考博客
P a r t I I I . Part\ III. P a r t III .
D u f f ′ s D e v i c e Duff's\ Device D u f f ′ s De v i ce
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> #include <vector> #include <numeric> void copyIntArray (std::vector<int >& src, std::vector<int >& dest, int size) { int rounds = size / 8 ; int i = 0 ; switch (size % 8 ) { case 0 : while (rounds-- > 0 ) { dest[i] = src[i++]; case 7 : dest[i] = src[i++]; case 6 : dest[i] = src[i++]; case 5 : dest[i] = src[i++]; case 4 : dest[i] = src[i++]; case 3 : dest[i] = src[i++]; case 2 : dest[i] = src[i++]; case 1 : dest[i] = src[i++]; }; } }int main () { int size = 20 ; std::vector<int > src (size, 0 ) , dest (20 ) ; std::iota (src.begin (), src.end (), 1 ); copyIntArray (src, dest, size); for (int i = 0 ; i < size; ++i) { std::cout << dest[i] << std::endl; } return 0 ; }
利用 switch 语句的特性进行非常奇怪的操作,这被叫做 D u f f ′ s d e v i c e Duff's\ device D u f f ′ s d e v i ce , 可以用来模拟 C++ 的协程,但是在 C++20 版本已经提供了官方封装的协程。
How does Duff’s Device work?
P a r t I V . Part\ IV. P a r t I V .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 #pragma once #include <optional> #include <stdexcept> template <typename T>class Ref { static_assert ( std::is_nothrow_move_constructible_v<T> ); static_assert ( std::is_nothrow_move_assignable_v<T> );public : Ref () requires std::default_initializable<T> : obj_ ( std::in_place ) {} Ref ( T&& obj ) : obj_ ( std::move ( obj ) ) {} Ref ( Ref&& other ) noexcept = default ; Ref& operator =( Ref&& other ) noexcept = default ; static Ref borrow ( const T& obj ) { Ref ret { uninitialized }; ret.borrowed_obj_ = &obj; return ret; } Ref borrow () const { Ref ret { uninitialized }; ret.borrowed_obj_ = obj_.has_value () ? &obj_.value () : borrowed_obj_; return ret; }#ifndef DISALLOW_REF_IMPLICIT_COPY Ref ( const Ref& other ) : obj_ ( other.get () ) {} Ref& operator =( const Ref& other ) { if ( this != &other ) { obj_ = other.get (); borrowed_obj_ = nullptr ; } return *this ; }#else Ref ( const Ref& other ) = delete ; Ref& operator =( const Ref& other ) = delete ;#endif ~Ref () = default ; bool is_owned () const { return obj_.has_value (); } bool is_borrowed () const { return not is_owned (); } const T& get () const { return obj_.has_value () ? *obj_ : *borrowed_obj_; } T& get_mut () { if ( not obj_.has_value () ) { throw std::runtime_error ( "attempt to mutate borrowed Ref" ); } return *obj_; } operator const T&() const { return get (); } operator T&() { return get_mut (); } const T* operator ->() const { return &get (); } T* operator ->() { return &get_mut (); } explicit operator std::string_view () const requires std::is_convertible_v<T, std::string_view> { return get (); } T release () { if ( obj_.has_value () ) { return std::move ( *obj_ ); }#ifndef DISALLOW_REF_IMPLICIT_COPY return get ();#else throw std::runtime_error ( "Ref::release() called on borrowed reference" );#endif }private : const T* borrowed_obj_ {}; std::optional<T> obj_ {}; struct uninitialized_t {}; static constexpr uninitialized_t uninitialized {}; explicit Ref ( uninitialized_t ) {} };template <typename T>static Ref<T> borrow ( const T& obj ) { return Ref<T>::borrow ( obj ); }
Q & A Q \& A Q & A
static_assert
是做什么用的?为什么这里要检查 std::is_nothrow_move_constructible_v<T>
和 std::is_nothrow_move_assignable_v<T>
?如果 T
类型不满足这些条件会怎么样?
默认构造函数 Ref()
后面的 requires std::default_initializable<T>
是什么意思?它和普通的构造函数有什么区别?
std::optional<T> obj_
和 const T* borrowed_obj_
这两个成员变量是如何协同工作来表示 “owned” 和 “borrowed” 状态的?为什么不直接用一个指针和一个布尔标志位?
struct uninitialized_t {}; static constexpr uninitialized_t uninitialized {};
和私有构造函数 explicit Ref(uninitialized_t)
这一套组合的目的是什么?为什么 borrow
函数需要这样创建一个 Ref
对象?
#ifndef DISALLOW_REF_IMPLICIT_COPY ... #else ... #endif
这段预处理指令是用来做什么的?为什么会有允许或禁止隐式拷贝的选项?拷贝构造函数和拷贝赋值运算符在 “owned” 和 “borrowed” 状态下是如何工作的?
operator const T&() const
和 operator T&()
这两个类型转换运算符为什么没有 explicit
关键字 (注释中提到了 NOLINT(*-explicit-*)
)?它们允许什么样的隐式转换,这在实际使用中会有什么好处或潜在风险?
T release()
函数在 Ref
是 “owned” 和 “borrowed” 状态时行为有什么不同?为什么在 “borrowed” 状态下(如果 DISALLOW_REF_IMPLICIT_COPY
被定义)会抛出异常?std::move(*obj_)
的作用是什么?
文件末尾的自由函数 template<typename T> static Ref<T> borrow(const T& obj)
和类内部的静态成员函数 static Ref borrow(const T& obj)
有什么区别?为什么会提供一个自由函数版本?这里的 static
用在自由函数模板上是什么意思?
除了类型转换运算符,代码还重载了 operator->() 和 operator->() const。这两个箭头运算符的重载允许我们像使用指针一样使用 Ref 对象(例如 ref_obj->member_func()),它们是如何实现这一点的?在 “owned” 和 “borrowed” 状态下,它们分别返回什么?
以上问题由 Gemini 生成。