c++ std::shared_ptr學習

背景

? ? ? ? c++中智能指針shared_ptr用于自動管理資源,通過引用計數來記錄資源被多少出地方使用。在不使用資源時,減少引用計數,如果引用計數為0,表示資源不會再被使用,此時會釋放資源。本文記錄對c++中std::shared_ptr的源碼學習。

為什么需要“智能”指針

? ? ? ?當我們需要在堆上分配對象時,可以通過new來創建,在不需要使用對象時,需要使用delete來釋放對象占用的資源,如下所示:

void foo() {...int *pi = new int(0);...delete pi;...
}

? ? ? ? 如果在delete之前,函數提前return,則new出來的內存不會被釋放,導致內存泄露。另外,如果一個對象在函數中被new出來,并作為返回值返回,但是使用者如果不清楚函數內部實現,也可能忘記delete調new出來的對象,導致內存泄露,如下所示:

int* foo() {return new int(0);
}void bar() {int *pi = foo();...return; // 因為不知道foo返回的int*是new出來的,所以沒有delete pi
}

? ? ? ? 對上面這種場景,還可能會出現一個指針被多處使用。為了確保指針在使用過程中沒有被delete,還需要關注指針使用的先后順序。但在迭代的過程中,這會導致指針的使用變得很難維護。為此,我們需要有一個“智能”的指針,維護我們new出來的對象,并在不需要的時候自動delete,釋放資源。

智能指針如何“智能”

? ? ? ? c++智能指針利用class的構造函數和析構函數來對指針管理,構造函數中將new出來的指針傳入,在析構函數中判斷如果沒人再使用,則會釋放指針。

? ? ? ? 如何判斷指針還被使用呢?答案是使用引用計數。只要指針被引用了,引用計數加1,當某一個引用不再使用(析構之后),則引用計數減1,如果指針不再被使用時,引用計數為0,此時即可delete指針,釋放資源。

? ? ? ? 那如何知道一個指針被引用了呢?答案就是復制構造和賦值構造。如果不知道什么是復制構造和賦值構造,可以先去學習下,以下是個簡單的例子:

class A {
public:// 構造函數A() {x = 0;}// 析構函數~A() {}// 復制構造A(const A &a) {x = a.x;}// 賦值構造A& operate=(cosnt A &a) {x = a.x;return *this;}
private:int x;
}int main() {A a1; // 構造函數A a2(a1); // 復制構造A a3 = a1; // 賦值構造
}
// 離開作用域,a1, a2, a3析構

shared_ptr概覽

? ? ? ? shared_ptr定義如下:

  /***  @brief  A smart pointer with reference-counted copy semantics.**  The object pointed to is deleted when the last shared_ptr pointing to*  it is destroyed or reset.*/template<typename _Tp>class shared_ptr : public __shared_ptr<_Tp>{...}

即shared_ptr繼承__shared_ptr,__shared_ptr定義如下:

  template<typename _Tp, _Lock_policy _Lp>class __shared_ptr: public __shared_ptr_access<_Tp, _Lp>{public:using element_type = typename remove_extent<_Tp>::type;...private:element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.}

? ? ? ? 從注釋中可以看出,__shared_ptr中有兩個成員變量:_M_ptr和_M_refcount。_M_ptr是智能指針管理的資源,_M_refcount是引用計數。再看下_M_refcount的類型定義,即__shared_count:

  template<_Lock_policy _Lp>class __shared_count{...private:_Sp_counted_base<_Lp>*  _M_pi;};template<_Lock_policy _Lp = __default_lock_policy>class _Sp_counted_base: public _Mutex_base<_Lp>{...private:_Atomic_word  _M_use_count;     // #shared...};typedef int _Atomic_word;

? ? ? ? 可以看出,引用計數本質是一個int值:_M_use_count。

? ? ? ? shared_ptr簡單的類圖示意如下:

? ? ? ? 根據智能指針實現方式,我們主要考慮構造函數、析構函數、復制構造函數、賦值構造函數。

構造函數

? ? ? ? 用以下代碼作為示例說明智能指針構造過程:

class A {
public:A() {cout << "construct A" << endl;}~A() {cout << "deconstruct A" << endl;}
};void test_init() {shared_ptr<A> pa(new A);
}

? ? ? ? 當test函數被調用是,創建了一個A指針,該指針作為shared_ptr入參傳入構造函數,shared_ptr構造函數調用過程如下:

// shared_ptr構造函數/***  @brief  Construct a %shared_ptr that owns the pointer @a __p.*  @param  __p  A pointer that is convertible to element_type*.*  @post   use_count() == 1 && get() == __p*  @throw  std::bad_alloc, in which case @c delete @a __p is called.*/template<typename _Yp, typename = _Constructible<_Yp*>>explicitshared_ptr(_Yp* __p) : __shared_ptr<_Tp>(__p) { }// __shared_ptr構造函數,本文考慮指針不是數組指針,即is_array<_Tp>::type() == false_typetemplate<typename _Yp, typename = _SafeConv<_Yp>>explicit__shared_ptr(_Yp* __p): _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type()){...}// 引用計數__shared_ptr::_M_refcount(__shared_count類型)構造函數template<typename _Ptr>__shared_count(_Ptr __p, /* is_array = */ false_type): __shared_count(__p){ }template<typename _Ptr>explicit__shared_count(_Ptr __p) : _M_pi(0){__try{_M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p);}__catch(...){delete __p;__throw_exception_again;}}// __shared_count::_M_pi構造函數// Counted ptr with no deleter or allocator supporttemplate<typename _Ptr, _Lock_policy _Lp>class _Sp_counted_ptr final : public _Sp_counted_base<_Lp>{public:explicit_Sp_counted_ptr(_Ptr __p) noexcept: _M_ptr(__p) { }...private:_Ptr             _M_ptr;};_Sp_counted_base() noexcept: _M_use_count(1), _M_weak_count(1) { }

? ? ? ?可以看到,new創建的指針被存在__shared_ptr::_M_ptr中,同時,__shared_ptr::_M_refcount構造函數中,new創建了_M_refcount::_M_pi,這樣,引用計數才不會因為__shared_ptr析構而消失,引用計數的生命周期應該與指針的生命周期一致,才能記錄指針被引用的次數。__Sp_counted_base構造函數中,初始化引用計數_M_use_count為1,表示當前只有1處在使用該指針。

????????構造函數調用結束之后,shared_ptr數據可簡單表示為:

? ? ? ? (為什么內存布局是這樣?可查看附錄中的測試代碼。)

復制構造

? ? ? ? 以下代碼被調用時,智能指針的復制構造函數被調用:

int test_copy() {shared_ptr<A> pa(new A);shared_ptr<A> pa_copy(pa);  // 復制構造
}

? ? ? ? 復制構造主要實現代碼如下:

  template<typename _Tp>class shared_ptr : public __shared_ptr<_Tp>{...public:...shared_ptr(const shared_ptr&) noexcept = default;  // 默認復制構造...}template<typename _Tp, _Lock_policy _Lp>class __shared_ptr: public __shared_ptr_access<_Tp, _Lp>{public:using element_type = typename remove_extent<_Tp>::type;...__shared_ptr(const __shared_ptr&) noexcept = default;  // 默認復制構造...private:...element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.}

? ? ? ? 復制構造使用編譯器生成的默認復制構造函數,偽代碼可以表示成:

template<typename _Tp>class shared_ptr : public __shared_ptr<_Tp>{...public:...shared_ptr(const shared_ptr& ptr) noexcept {_M_ptr = __ptr._M_ptr;_M_refcount.__shared_count::__shared_count(__ptr._M_refcount);}...}

? ? ? ? 繼續看__shared_count的復制構造函數:

  template<_Lock_policy _Lp>class __shared_count{...public:__shared_count(const __shared_count& __r) noexcept: _M_pi(__r._M_pi){if (_M_pi != 0)_M_pi->_M_add_ref_copy();}...private:..._Sp_counted_base<_Lp>*  _M_pi;}template<_Lock_policy _Lp = __default_lock_policy>class _Sp_counted_base: public _Mutex_base<_Lp>{public:...void_M_add_ref_copy(){ __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }...private:_Sp_counted_base(_Sp_counted_base const&) = delete;_Sp_counted_base& operator=(_Sp_counted_base const&) = delete;_Atomic_word  _M_use_count;     // #shared_Atomic_word  _M_weak_count;    // #weak + (#shared != 0)};namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION// Functions for portable atomic access.// To abstract locking primitives across all thread policies, use:// __exchange_and_add_dispatch// __atomic_add_dispatch
#ifdef _GLIBCXX_ATOMIC_BUILTINSstatic inline _Atomic_word __exchange_and_add(volatile _Atomic_word* __mem, int __val){ return __atomic_fetch_add(__mem, __val, __ATOMIC_ACQ_REL); }...static inline void__attribute__ ((__unused__))__atomic_add_dispatch(_Atomic_word* __mem, int __val){
#ifdef __GTHREADSif (__gthread_active_p())__atomic_add(__mem, __val);else__atomic_add_single(__mem, __val);
#else__atomic_add_single(__mem, __val);
#endif}_GLIBCXX_END_NAMESPACE_VERSION
} // namespace

? ? ? ? 可以看到,復制構造函數最終就是將引用計數加1,且是通過原子操作來進行加1的,由此可見:智能指針是線程安全的!至于如何實現的線程安全,本文暫不深究。

賦值構造

? ? ? ? 類似地,以下函數調用時,智能指針的賦值構造函數被調用:

int test_assgin() {shared_ptr<A> pa(new A);shared_ptr<A> pa_assign(new A);pa_assign = pa;  // 賦值構造
}

? ? ? ? 比復制構造稍微復雜一點,賦值構造的實現如下:

  template<typename _Tp>class shared_ptr : public __shared_ptr<_Tp>{...public:...shared_ptr& operator=(const shared_ptr&) noexcept = default;...}template<typename _Tp, _Lock_policy _Lp>class __shared_ptr: public __shared_ptr_access<_Tp, _Lp>{public:using element_type = typename remove_extent<_Tp>::type;...__shared_ptr& operator=(const __shared_ptr&) noexcept = default;...private:...element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.}template<_Lock_policy _Lp>class __shared_count{...public:...__shared_count&operator=(const __shared_count& __r) noexcept{_Sp_counted_base<_Lp>* __tmp = __r._M_pi;if (__tmp != _M_pi){if (__tmp != 0)__tmp->_M_add_ref_copy();if (_M_pi != 0)_M_pi->_M_release();_M_pi = __tmp;}return *this;}...private:..._Sp_counted_base<_Lp>*  _M_pi;}template<typename _Ptr, _Lock_policy _Lp>class _Sp_counted_ptr final : public _Sp_counted_base<_Lp>{public:...virtual void_M_dispose() noexcept{ delete _M_ptr; }virtual void_M_destroy() noexcept{ delete this; }...}template<_Lock_policy _Lp = __default_lock_policy>class _Sp_counted_base: public _Mutex_base<_Lp>{public:...void_M_add_ref_copy(){ __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }...void_M_release() noexcept{// Be race-detector-friendly.  For more info see bits/c++config._GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1){_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);_M_dispose();// There must be a memory barrier between dispose() and destroy()// to ensure that the effects of dispose() are observed in the// thread that runs destroy().// See http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.htmlif (_Mutex_base<_Lp>::_S_need_barriers){__atomic_thread_fence (__ATOMIC_ACQ_REL);}// Be race-detector-friendly.  For more info see bits/c++config._GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count,-1) == 1){_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);_M_destroy();}}}...private:_Sp_counted_base(_Sp_counted_base const&) = delete;_Sp_counted_base& operator=(_Sp_counted_base const&) = delete;_Atomic_word  _M_use_count;     // #shared_Atomic_word  _M_weak_count;    // #weak + (#shared != 0)};namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION...static inline _Atomic_word__attribute__ ((__unused__))__exchange_and_add_dispatch(_Atomic_word* __mem, int __val){
#ifdef __GTHREADSif (__gthread_active_p())return __exchange_and_add(__mem, __val);elsereturn __exchange_and_add_single(__mem, __val);
#elsereturn __exchange_and_add_single(__mem, __val);
#endif}..._GLIBCXX_END_NAMESPACE_VERSION
} // namespace

? ? ? ? 賦值構造會將被復制的對象引用計數加1,同時,將自身的引用計數減1,如果減1之前,自身的引用計數已經為1,則將釋放持有的指針:_M_dispose(),并且weak_count=1時,也會將自身delete掉:_M_destroy()。整個操作也是線程安全的。

析構函數

? ? ? ? 當類離開其作用域時,析構函數會被調用:

int test_destroy() {{shared_ptr<A> pa(new A);}// 此時, pa的析構函數已經被調用
}

? ? ? ? 析構函數的實現如下:

  // shared_ptr沒有顯式定義析構函數template<typename _Tp, _Lock_policy _Lp>class __shared_ptr: public __shared_ptr_access<_Tp, _Lp>{public:using element_type = typename remove_extent<_Tp>::type;...~__shared_ptr() = default;...private:...element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.}template<_Lock_policy _Lp>class __shared_count{...public:...~__shared_count() noexcept{if (_M_pi != nullptr)_M_pi->_M_release();}...private:..._Sp_counted_base<_Lp>*  _M_pi;}

? ? ? ? 析構函數調用引用計數的_M_release(),即引用計數減1,且如果資源不再使用,則釋放資源。

像使用指針一樣

? ? ? ? 在使用智能指針時,為了能像使用指針一樣,智能指針對操作符*、->進行了重載,我們就能像下面這樣使用智能指針了:

struct B {int b;
};void test() {B *pb = new B;shared_ptr<B> shared_pb(pb);shared_pb->b = 1; // 類似于pb->b = 1B b = *shared_pb; // 類似于B b = *pb;
}

? ? ? ? 重載操作符的實現如下:

  template<typename _Tp, _Lock_policy _Lp>class __shared_ptr_access<_Tp, _Lp, true, false>{public:using element_type = typename remove_extent<_Tp>::type;#if __cplusplus <= 201402L[[__deprecated__("shared_ptr<T[]>::operator* is absent from C++17")]]element_type&operator*() const noexcept{__glibcxx_assert(_M_get() != nullptr);return *_M_get();}[[__deprecated__("shared_ptr<T[]>::operator-> is absent from C++17")]]element_type*operator->() const noexcept{_GLIBCXX_DEBUG_PEDASSERT(_M_get() != nullptr);return _M_get();}
#endif...private:element_type*_M_get() const noexcept{ return static_cast<const __shared_ptr<_Tp, _Lp>*>(this)->get(); }};template<typename _Tp, _Lock_policy _Lp>class __shared_ptr: public __shared_ptr_access<_Tp, _Lp>{public:using element_type = typename remove_extent<_Tp>::type;...element_type*get() const noexcept{ return _M_ptr; }...private:element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.};

總結

? ? ? ? 總結一下,智能指針,就是申請資源(本文中的例子是指針)之后,將資源交給智能指針,智能指針復制、賦值時原子增加、減少引用計數,并在引用計數為0,即沒有人再使用資源時,釋放資源,并將引用計數釋放掉(引用計數也是new申請出來的),以此來實現“智能”管理資源。

附錄

shared_ptr內存布局測試

? ? ? ? 測試代碼如下:

# include <memory>
# include <iostream>
# include <stdint.h>using namespace std;class A {
public:A() {cout << "construct A" << endl;}~A() {cout << "deconstruct A" << endl;}
};/*__shared_ptr:element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.__shared_count:_Sp_counted_base<_Lp>*  _M_pi;_Sp_counted_ptr:void *vptr;_Atomic_word  _M_use_count;     // #shared_Atomic_word  _M_weak_count;    // #weak + (#shared != 0)_Ptr             _M_ptr;*/int main() {{A *pa = new A();shared_ptr<A> shared_pa(pa);char *addr = (char*)&shared_pa;void *shared_pa_addr = *((void**)addr);void *_M_pi = *((void**)(addr+8));printf("pa: 0x%X, shared_pa_addr: 0x%X, _M_pi: 0x%X\n", pa, shared_pa_addr, _M_pi);addr = (char*)_M_pi;void *vptr = *((void**)addr);int _M_use_count = *((int*)(addr+8));int _M_weak_count = *((int*)(addr+12));void *_M_ptr = *((void**)(addr+16));printf("vptr: 0x%X, _M_use_count: %d, _M_weak_count: %d, _M_ptr: 0x%X\n", vptr, _M_use_count, _M_weak_count, _M_ptr);}cout << "move scope" << endl;
}

面試要求寫一個智能指針

? ? ? ? 面試中可能會被要求實現一個智能指針,先不考慮線程安全、各種場景的兼容,可以對上面代碼進行精簡,僅對核心邏輯做實現:

#ifndef M_SMART_POINTER_H
#define M_SMART_POINTER_Htemplate<typename T>
class MSmartPointer {
public:MSmartPointer(T *ptr_) {ptr = ptr_;ref_count_ptr = new int(1);}MSmartPointer(const MSmartPointer &r) {Copy(r);}MSmartPointer& operator=(const MSmartPointer &r) {if (r.ptr != ptr) {Release();Copy(r);}return *this;}~MSmartPointer() {Release();}T& operator*() {return *ptr;}T* operator->() {return ptr;}int ref_count() {return *ref_count_ptr;}
private:void Copy(const MSmartPointer &r) {ptr = r.ptr; // 持有指針ref_count_ptr = r.ref_count_ptr; // 持有指針對應的引用計數if (ref_count_ptr)(*ref_count_ptr)++; // 引用計數加1}void Release() {if (!ref_count_ptr) return;if (--(*ref_count_ptr) <= 0) { // 引用計數減1,如果引用計數小于等于0,代表沒人用了,需要釋放資源delete ref_count_ptr; // 釋放引用計數ref_count_ptr = nullptr;delete ptr; // 釋放持有的指針ptr = nullptr;}}private:T *ptr;int *ref_count_ptr;
};#endif

? ? ? ? 附上測試代碼:

void test_m_smart_pointer() {cout << "test_m_smart_pointer" << endl;{MSmartPointer<Test> p(new Test);cout << "1. ref_count: " << p.ref_count() << endl;p->t = 1;cout << "1. t: " << p->t << endl;{MSmartPointer<Test> p1(p);cout << "2. ref_count: " << p.ref_count() << endl; (*(p1)).t = 2;cout << "2. t: " << p->t << endl;MSmartPointer<Test> p2(new Test);p2 = p;cout << "3. ref_count: " << p.ref_count() << endl; }cout << "4. ref_count: " << p.ref_count() << endl; }cout << "move scope" << endl;
}

線程安全思考

? ? ? ? 在對智能指針的引用計數進行修改時,都使用額原子操作,因此引用計數的加減都是現成安全的。但是,在釋放資源時,其整個釋放的操作為非原子的。如果在釋放資源的過程,發生了復制、賦值,則就可能出現資源被釋放,但是還有地方在引用的問題。不考慮多線程場景,以下面的代碼舉例:

void test_atomic_thread_safe() {A *pa = new A;shared_ptr<A> shared_pa(pa);shared_pa.__shared_ptr<A>::~__shared_ptr<A>();shared_ptr<A> shared_pb(pa);cout << "test call deconstruct: " << shared_pb.use_count() << endl;
}

? ? ? ? ?這段代碼運行結果如下:

construct A
deconstruct A
test call deconstruct: 1
deconstruct A
deconstruct A
free(): double free detected in tcache 2
[1]    6372 abort (core dumped)  ./output/main

? ? ? ? 可以看到,在shared_pa被析構之后,再去對其復制,將導致持有的指針不再有效。但是,正常情況下,誰會主動掉析構函數呢?如果我們不主動調析構函數,則析構函數被調用時,我們也取不到對象了,更別提對其引用。所以總體看來,智能指針還是線程安全的。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/11821.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/11821.shtml
英文地址,請注明出處:http://en.pswp.cn/web/11821.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

攻防世界PHP2

1、打開靶機鏈接http://61.147.171.105:49513/&#xff0c;沒有發現任何線索 2、嘗試訪問http://61.147.171.105:49513/index.php&#xff0c;頁面沒有發生跳轉 3、嘗試將訪問 嘗試訪問http://61.147.171.105:49513/index.phps index.php 和 index.phps 文件之間的主要區別在于…

GNU Radio創建時間戳 C++ OOT塊

文章目錄 前言一、創建自定義的 C OOT 塊1、創建 timestamp_sender C OOT 模塊①、創建 timestamp_sender OOT 塊②、修改 C 代碼 2、創建 timestamp_receiver C OOT 模塊①、創建 timestamp_receiver OOT 塊②、修改 C 代碼 3、創建 delayMicroSec C OOT 模塊①、創建 delayMi…

Vue3實戰筆記(20)—封裝頭部導航組件

文章目錄 前言一、封裝頭部導航欄二、使用步驟總結 前言 Vue 3 封裝頭部導航欄有助于提高代碼復用性、統一風格、降低維護成本、提高可配置性和模塊化程度&#xff0c;同時還可以實現動態渲染等功能&#xff0c;有利于項目開發和維護。 一、封裝頭部導航欄 封裝頭部導航欄&am…

HFSS學習-day4-建模操作

通過昨天的學習&#xff0c;我們已經熟悉了HFSS的工作環境&#xff1b;今天我們來講解HFSS中創建物體模型的縣體步驟和相關操作。物體建模是HFSS仿真設計工作的第一步&#xff0c;HFSS中提供了諸如矩形、圓面、長方體圓柱體和球體等多種基本模型(Primitive)&#xff0c;這些基本…

新書速覽|MATLAB科技繪圖與數據分析

提升你的數據洞察力&#xff0c;用于精確繪圖和分析的高級MATLAB技術。 本書內容 《MATLAB科技繪圖與數據分析》結合作者多年的數據分析與科研繪圖經驗&#xff0c;詳細講解MATLAB在科技圖表制作與數據分析中的使用方法與技巧。全書分為3部分&#xff0c;共12章&#xff0c;第1…

tp8 設置空控制器和空方法

1、空控制器 單應用模式下&#xff0c;我們可以給項目定義一個Error控制器類 <?phpnamespace app\controller;class Error {/*** 空控制器中重寫魔術方法__call可以實現自定義錯誤提示&#xff0c;在這里可以提示找不到控制器* 注意&#xff1a;在基礎控制器BaseControll…

精英都是時間控!職場精英的完美一天~~~谷歌FB都在用的時間管理術!

如何超高效使用24小時 每個人的一天都只有24小時&#xff0c;使用時間的方法將決定整個人生。時間管理術并不提倡把自己忙死榨干&#xff0c;而是通過在合適的時間做合適的事情&#xff0c;把大腦機能發揮到極致&#xff0c;從而提高效率&#xff0c;節省下更多時間用于生活與…

(項目)-KDE巡檢報告(模板

金山云于12月26日對建行共計【30】個KDE集群,合計【198】臺服務器進行了巡檢服務。共發現系統風險【135】條,服務風險【1912】條,服務配置風險【368】條。 一、系統風險 1、風險分析(圖片+描述) (1)磁盤使用率高 問題描述多個集群的多臺服務器磁盤使用率較高,遠超過…

答辯PPT模版如何選擇?aippt快速生成

這些網站我愿稱之為制作答辯PPT的神&#xff01; 很多快要畢業的同學在做答辯PPT的時候總是感覺毫無思路&#xff0c;一竅不通。但這并不是你們的錯&#xff0c;對于平時沒接觸過相關方面&#xff0c;第一次搞答辯PPT的人來說&#xff0c;這是很正常的一件事。一個好的答辯PPT…

右鍵使用VSCode打開文件/文件夾目錄

右鍵使用VSCode打開文件/文件夾目錄 使用新電腦或清空了注冊列表之后&#xff0c;點擊右鍵“使用vscode”打開文件夾消失了&#xff0c;可以通過更改注冊列表增加回來。 實現&#xff1a; 右鍵在目錄空白處使用vscode打開目錄右鍵-用vscode(當前窗口)打開文件或目錄 右鍵-用vs…

簡述RocketMQ系統架構及其相關概念

一、概述 RocketMQ是一款高性能、高吞吐量的分布式消息隊列系統&#xff0c;它采用了分布式架構&#xff0c;支持多生產者和消費者并發讀寫&#xff0c;具有高可用性、高吞吐量、低延遲等特點。本文將對RocketMQ的系統架構進行詳細解析。 二、架構設計 RocketMQ采用了分布式架…

入門物聯網就是這么簡單——青創智通

工業物聯網解決方案-工業IOT-青創智通 MQTT&#xff0c;全稱為Message Queuing Telemetry Transport&#xff0c;是一種輕量級的發布/訂閱消息傳輸協議&#xff0c;廣泛應用于物聯網領域。 MQTT協議以其高效、可靠、靈活的特性&#xff0c;成為物聯網設備間通信的理想選擇。本…

升級版ComfyUI InstantID 換臉:FaceDetailer + InstantID + IP-Adapter

在使用ComfyUI的InstantID進行人臉替換時&#xff0c;一個常見問題是該工具傾向于保留原始參考圖的構圖&#xff0c;即使用戶的提示詞與之不符。 例如&#xff0c;即使用戶提供的是大頭照并請求生成全身照&#xff0c;結果仍是大頭照&#xff0c;沒有顯示出用戶所期望的構圖。…

MySQL_DDL語句

1.Data類臨時數據的弊端 我們之前在將ServletJSP配合處理請求的過程中 數據庫起到一個存取數據的作用 但是我們之前的案例中 數據是在Data類中臨時定義的 并不是從數據庫中獲取的 這樣做是不好的 因為每一次服務器關閉之后 那么部署在其上的類也會隨著卸載 緊接著和類相掛鉤的靜…

基于C#開發web網頁管理系統模板流程-登錄界面

前言&#xff0c;首先介紹一下本項目將要實現的功能 &#xff08;一&#xff09;登錄界面 實現一個不算特別美觀的登錄窗口&#xff0c;當然這一步跟開發者本身的設計美學相關&#xff0c;像蒟蒻博主就沒啥藝術細胞&#xff0c;勉強能用能看就行…… &#xff08;二&#xff09…

使用Tkinter開發Python棋盤游戲

使用 Tkinter 開發一個簡單的棋盤游戲是很有趣的&#xff01;下面是一個示例&#xff0c;演示如何使用 Tkinter 創建一個簡單的五子棋游戲&#xff1a;這個是我通過幾個夜晚整理出來的解決方案和實際操作教程。 1、問題背景 目標是開發一個 Python 棋盤游戲&#xff0c;玩家可…

web測試中,各類web控件測試點總結

一、界面檢查 進入一個頁面測試&#xff0c;首先是檢查title&#xff0c;頁面排版&#xff0c;字段等&#xff0c;而不是馬上進入文本框校驗 1、頁面名稱title是否正確 2、當前位置是否可見 您的位置&#xff1a;xxx>xxxx 3、文字格式統一性 4、排版是否整齊 5、列表項…

【--ckpt_save_interval 1 -- sync_bn】

在深度學習和機器學習的上下文中&#xff0c;–ckpt_save_interval 1 和 --sync_bn 是命令行參數&#xff0c;它們通常用于配置訓練過程。 不過&#xff0c;這兩個參數并不是所有框架或工具都通用的&#xff0c;但我可以根據常見的用法來解釋它們。 --ckpt_save_interval 1這…

人力資源管理:員工體驗平臺設計

員工體驗是員工的感受&#xff0c;是員工作為企業一份子觀察到、感受到和與之互動的一切&#xff0c;包含企業為員工提供的物質條件、人文環境等各方面的內容。 是在工作過程中接觸到的所有接觸點所產生的對自己與用人單位關系的整體感知&#xff0c;員工體驗從員工入職開始貫…

使用Go和JavaScript爬取股吧動態信息的完整指南

引言 在現代金融生態系統中&#xff0c;信息流動的速度和效率對于市場的健康和投資者的成功至關重要。股市信息&#xff0c;特別是來自活躍交流平臺如股吧的實時數據&#xff0c;為投資者提供了一個獨特的視角&#xff0c;幫助他們洞察市場趨勢和投資者情緒。這些信息不僅能夠…