sssssssssssssssshare_ptrrrrrrrrrrrrrrrrrrrrrrrrr

news2025/1/8 1:13:30

智能指针——shared_ptr的原理及仿写


shared_ptr的原理及仿写


共享指针允许多个指针指向同一份数据,因为它使用了引用计数,每多一个指针指向这个数据,引用技术加一,每销毁一个指针,引用技术减一,如果引用计数为0,则delete这个数据。
但是共享指针也不能将同一个裸指针赋值给多个智能指针,因为这样会是两个独立的共享指针,它们会分别计数,也就是意味着到时候它们会重复释放。
第一个没有计数器设计的版本:
template<typename T>
class MyShared_ptr {
private:
    //内部指针
    T* m_ptr;
    //计数器
    long* m_count;
public:
    //以裸指针构造共享指针,默认为nullptr
    //禁止隐式构造
    explicit MyShared_ptr(T* ptr = nullptr) : m_ptr(ptr) {
        //计数器初始化为1
        m_count = new long(1);
    }
    //以MyUnique_ptr右值构造共享指针
    MyShared_ptr(MyUnique_ptr<T>&& src) {
        //销毁独占指针,取出裸指针
        m_ptr = src.release();
        //计数器初始化为1
        m_count = new long(1);
    }
    //拷贝构造函数
    MyShared_ptr(const MyShared_ptr& src) {
        m_ptr = src.m_ptr;
        //拷贝计数器指针
        m_count = src.m_count;
        //计数器加一
        (*m_count)++;
    }
    //拷贝赋值
    MyShared_ptr& operator=(const MyShared_ptr& src) {
        if (&src == this) return *this;
        //如果自身不为空,先调用析构函数
        if (m_ptr != nullptr) {
            this->~MyShared_ptr();
        }
        m_ptr = src.m_ptr;
        m_count = src.m_count;
        (*m_count)++;
        return *this;
    }
    //以MyUnique_ptr右值 拷贝赋值
    MyShared_ptr& operator=(const MyUnique_ptr<T>&& src) {
        if (m_ptr != nullptr) {
            this->~MyShared_ptr();
        }
        //销毁独占指针,取出裸指针
        m_ptr = src.release();
        m_count = new long(1);
        return *this;
    }
    //析构函数
    ~MyShared_ptr() {
        //计数器减一
        (*m_count)--;
        //如果计数器为0,则释放指针空间和计数器空间
        if (0 == *m_count) {
            delete m_ptr;
            delete m_count;
            m_ptr = nullptr;
            m_count = nullptr;
        }
    }
    //获得内部指针地址
    T* get() {
        return m_ptr;
    }
    //重置指针
    void reset(T* ptr = nullptr) {
        //先调用析构函数
        this->~MyShared_ptr();
        m_ptr = ptr;
        m_count = new long(1);
    }
    //返回当前引用计数
    long use_count() {
        return *m_count;
    }
    //转换函数 bool
    operator bool() const {
        return m_ptr != nullptr;
    }
    //当前共享指针是否唯一
    bool unique() {
        if (1 == *m_count) return true;
        else return false;
    }
    //成员函数交换指针和计数器
    void swap(MyShared_ptr<T>& src) {
        T* temp_ptr;
        long* temp_count;
        temp_ptr = src.m_ptr;
        temp_count = src.m_count;
        src.m_ptr = m_ptr;
        src.m_count = m_count;
        m_ptr = temp_ptr;
        m_count = temp_count;
    }
    //重载*
    T& operator*() const {
        return *m_ptr;
    }
    //重载->
    T* operator->() const {
        return (&**this);
    }
};
//全局函数,有右值引用make_MyShared
template<class T>
MyShared_ptr<T> make_MyShared(MyShared_ptr<T>&& src) {
    return MyShared_ptr<T>(forward<MyShared_ptr<T>>(src));
}

有计数器设计的版本
//计数器设计
template<typename T>
class MyCount {
private:
    //分别存储引用计数和弱指针计数
    long use_count;
    long weak_count;
public:
    //计数器的初始化
    MyCount(long u,long w) : use_count(u), weak_count(w) {}
    //弱指针计数++
    void Increase_weak_count() {
        ++weak_count;
    }
    //弱指针计数--
    void Decrease_weak_count() {
        --weak_count;
    }
    //引用计数++
    void Increase_use_count() {
        ++use_count;
    }
    //引用计数--
    void Decrease_use_count() {
        --use_count;
    }
    //获得引用计数
    long get_use_count() {
        return use_count;
    }
    //释放传入对象的内存
    void DestroyPtr(T* ptr) {
        delete ptr;
    }
    //若没有弱指针,则释放计数器
    void DestroyThis() {
        if(0 == weak_count)
            delete this;
    }
};

template<typename T>
class MyShared_ptr {
private:
    //内部指针
    T* m_ptr;
    //计数器指针
    MyCount<T>* m_count;
public:
    //以裸指针构造共享指针
    explicit MyShared_ptr(T* ptr = nullptr) : m_ptr(ptr) {
        //初始化计数器为 引用1 弱指针0
        m_count = new MyCount<T>(1,0);
    }
    //以独占指针构造共享指针
    MyShared_ptr(MyUnique_ptr<T>&& src) {
        //销毁独占指针并取出裸指针
        m_ptr = src.release();
        //初始化计数器为 引用1 弱指针0
        m_count = new MyCount<T>(1, 0);
    }
    //拷贝构造
    MyShared_ptr(const MyShared_ptr& src) {
        m_ptr = src.m_ptr;
        m_count = src.m_count;
        //引用计数++
        m_count->Increase_use_count();
    }
    //拷贝赋值
    MyShared_ptr& operator=(const MyShared_ptr& src) {
        if (&src == this) return *this;
        if (m_ptr != nullptr) {
            this->~MyShared_ptr();
        }
        m_ptr = src.m_ptr;
        m_count = src.m_count;
        //引用计数++
        m_count->Increase_use_count();
        return *this;
    }
    //拷贝独占指针赋值
    MyShared_ptr& operator=(const MyUnique_ptr<T>&& src) {
        if (m_ptr != nullptr) {
            this->~MyShared_ptr();
        }
        m_ptr = src.release();
        m_count = new MyCount<T>(1, 0);
        return *this;
    }
    //析构函数
    ~MyShared_ptr() {
        //引用计数--
        m_count->Decrease_use_count();
        //如果引用计数为零
        if (0 == m_count->get_use_count()) {
            //释放指针
            m_count->DestroyPtr(m_ptr);
            //销毁计数器(计数器内部判断有无弱指针)
            m_count->DestroyThis();
            m_ptr = nullptr;
            m_count = nullptr;
        }
    }
    //获得内部指针地址
    T* get() {
        return m_ptr;
    }
    //重置共享指针为新指针
    void reset(T* ptr = nullptr) {
        this->~MyShared_ptr();
        m_ptr = ptr;
        m_count = new MyCount<T>(1, 0);
    }
    //返回引用计数
    long use_count() {
        return m_count->get_use_count();
    }
    //转换函数
    operator bool() const {
        return m_ptr != nullptr;
    }
    //是否唯一
    bool unique() {
        if (1 == m_count->get_use_count()) return true;
        else return false;
    }
    //交换指针和计数器
    void swap(MyShared_ptr<T>& src) {
        T* temp_ptr;
        MyCount<T>* temp_count;
        temp_ptr = src.m_ptr;
        temp_count = src.m_count;
        src.m_ptr = m_ptr;
        src.m_count = m_count;
        m_ptr = temp_ptr;
        m_count = temp_count;
    }
    //重载*
    T& operator*() const {
        return *m_ptr;
    }
    //重载->
    T* operator->() const {
        return (&**this);
    }
    //设置弱指针类为友元类
    template<typename T>
    friend class MyWeak_ptr;
};

//全局函数,有右值引用make_MyShared
template<class T>
MyShared_ptr<T> make_MyShared(MyShared_ptr<T>&& src) {
    return MyShared_ptr<T>(forward<MyShared_ptr<T>>(src));
}
222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222

C++ 11 智能指针shared_ptr类成员函数

李小虎

李小虎

关注自动驾驶/机器人,RPC,深度学习,Linux

​关注他

2 人赞同了该文章

C++ 11 模板库的 <memory> 头文件中定义的智能指针,即 shared_ptr 模板类,用来管理指针的存储,提供有限的内存回收函数,可同时与其他对象共享该管理功能,从而帮助彻底消除内存泄漏和悬空指针的问题。

shared_ptr 类型的对象能够获得指针的所有权并共享该所有权:一旦他们获得所有权,指针的所有者组就会在最后一个释放该所有权时负责删除该指针。

shared_ptr 对象一旦它们自己被销毁,或者它们的值因赋值操作或显式调用 shared_ptr::reset 而改变时,就会释放它们共同拥有的对象的所有权。一旦通过指针共享所有权的所有 shared_ptr 对象都释放了该所有权,则删除托管对象(通常通过调用 ::delete,也可以在构造时指定不同的删除器)。

  1. 同一个shared_ptr被多个线程读,是线程安全的;
  2. 同一个shared_ptr被多个线程写,不是线程安全的;
  3. 共享引用计数的不同的shared_ptr被多个线程写,是线程安全的。

shared_ptr 对象只能通过复制它们的值来共享所有权:如果两个 shared_ptr 是从同一个(非shared_ptr)指针构造(或制造)的,它们都将拥有该指针而不共享它,当其中一个释放时会导致潜在的访问问题它(删除其托管对象)并将另一个指向无效位置。

此外,shared_ptr 对象可以共享一个指针的所有权,同时指向另一个对象。这种能力被称为别名(参见构造函数),通常用于在拥有成员对象时指向成员对象。因此,一个 shared_ptr 可能与两个指针相关:

1)一个存储的指针,即它所指向的指针,以及它用 operator* 取消引用的指针。

2)一个所有者的指针(可能是共享的),它是所有权组负责在某个时间点删除的指针,并计为使用。

通常,存储指针和所有者指针指向同一个对象,但别名 shared_ptr 对象(使用别名构造函数及其副本构造的对象)可能指向不同的对象。不拥有任何指针的 shared_ptr 称为null shared_ptr。不指向任何对象的 shared_ptr 称为null shared_ptr 并且不应取消引用。请注意,空的 shared_ptr 不一定是null shared_ptr,null shared_ptr 也不一定是空的 shared_ptr。shared_ptr 对象通过提供对它们通过运算符 * 和 -> 指向的对象的访问来复制有限的指针功能。出于安全原因,它们不支持指针算术。类似weak_ptr,能够与 shared_ptr 对象共享指针,而无需拥有它们。shared_ptr 有以下成员函数:

(1)构造函数

shared_ptr的构造函数根据使用的参数类型构造 shared_ptr 对象:

1) 默认构造函数:constexpr shared_ptr() noexcept;

2) 从空指针构造:constexpr shared_ptr(nullptr_t) : shared_ptr() {}

3) 从指针构造:template <class U> explicit shared_ptr (U* p);

auto sp_sub = std::shared_ptr<Sub<Message>>(sub, del(addr)); //sub为new创建的对象

4) 从指针 + 删除器构造:template <class U, class D> shared_ptr (U* p, D del); template <class D> shared_ptr (nullptr_t p, D del);

5) 从指针 + 删除器 + 分配器构造:template <class U, class D, class Alloc> shared_ptr (U* p, D del, Alloc alloc); template <class D, class Alloc> shared_ptr (nullptr_t p, D del, Alloc alloc);

6) 复制构造函数:shared_ptr (const shared_ptr& x) noexcept; template <class U> shared_ptr (const shared_ptr<U>& x) noexcept;

7) 从weak_ptr 复制:template <class U> explicit shared_ptr (const weak_ptr<U>& x);

8) 移动构造函数:shared_ptr (shared_ptr&& x) noexcept; template <class U> shared_ptr (shared_ptr<U>&& x) noexcept;

9) 从其他类型的托管指针移动:template <class U> shared_ptr (auto_ptr<U>&& x); template <class U, class D> shared_ptr (unique_ptr<U,D>&& x);

10) 别名构造函数:template <class U> shared_ptr (const shared_ptr<U>& x, element_type* p) noexcept;

默认构造函数 1) 和 2)对象为空(不拥有指针,使用计数为零)。从指针构造3)该对象拥有 p,将使用计数设置为 1。从指针 + 删除器构造 4)与 3) 相同,但该对象还拥有删除器 del 的所有权(并在某些时候需要删除 p 时使用它)。从指针 + 删除器 + 分配器构造 5)与 4) 相同,但内部使用所需的任何内存都是使用 alloc 分配的(对象保留一份副本,但不取得所有权)。复制构造函数 6)如果 x 不为空,则对象共享 x 资产的所有权并增加使用次数。如果 x 为空,则构造一个空对象(如同默认构造)。从weak_ptr 7) 复制同上6),除了如果 x 已经过期,则抛出 bad_weak_ptr 异常。移动构造函数 8)该对象获取由 x 管理的内容,包括其拥有的指针。 x 变成一个空对象(就像默认构造的一样)。从其他类型的托管指针移动 9)对象获取由 x 管理的内容并将使用计数设置为 1。放弃的对象变为空,自动失去指针的所有权。别名构造函数 10)同6),除了存储的指针是p。该对象不拥有 p,也不会管理其存储。相反,它共同拥有 x 的托管对象并算作 x 的一种额外使用。它还将在发布时删除 x 的指针(而不是 p)。它可以用来指向已经被管理的对象的成员。

1) p: 其所有权被对象接管的指针。此指针值不应已由任何其他托管指针管理(即,此值不应来自托管指针上的调用成员 get)。U* 应隐式转换为 T*(其中 T 是 shared_ptr 的模板参数)。

2) del: 用于释放拥有的对象的删除器对象。这应该是一个可调用对象,将指向 T 的指针作为其函数调用的参数(其中 T 是 shared_ptr 的模板参数)。

3) alloc:用于分配/取消分配内部存储的分配器对象。

4) X: 托管指针类型的对象。U* 应隐式转换为 T*(其中 T 是 shared_ptr 的模板参数)。
#include <iostream> 
#include <memory>  
struct C {int* data;};  
int main () {   
std::shared_ptr<int> p1;   
std::shared_ptr<int> p2 (nullptr);   
std::shared_ptr<int> p3 (new int);   
std::shared_ptr<int> p4 (new int, std::default_delete<int>());   
std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());   
std::shared_ptr<int> p6 (p5);   std::shared_ptr<int> p7 (std::move(p6));   
std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));   
std::shared_ptr<C> obj (new C);   
std::shared_ptr<int> p9 (obj, obj->data);    
std::cout << "use_count:\n";   
std::cout << "p1: " << p1.use_count() << '\n';   std::cout << "p2: " << p2.use_count() << '\n';   std::cout << "p3: " << p3.use_count() << '\n';   std::cout << "p4: " << p4.use_count() << '\n';   std::cout << "p5: " << p5.use_count() << '\n';   std::cout << "p6: " << p6.use_count() << '\n';   std::cout << "p7: " << p7.use_count() << '\n';   std::cout << "p8: " << p8.use_count() << '\n';   std::cout << "p9: " << p9.use_count() << '\n';   return 0; }

(2)析构函数

析构函数的作用是销毁shared_ptr对象。 但是,在此之前,根据成员 use_count 的值,它可能会产生以下副作用:

1)如果 use_count 大于 1(即该对象与其他 shared_ptr 对象共享其托管对象的所有权):与其共享所有权的其他对象的使用计数减 1。
2)如果 use_count 为 1(即对象是托管指针的唯一所有者):删除其拥有指针所指向的对象(如果 shared_ptr 对象是用特定的删除器构造的,则调用此函数;否则,函数使用运算符 删除)。
3)如果 use_count 为零(即对象为空),则该析构函数没有副作用。

用法举例:
#include <iostream> #include <memory> int main() { auto deleter = [](int* p) { std::cout << "[deleter called]\n"; delete p; }; std::shared_ptr<int> foo(new int, deleter); std::cout << "use_count: " << foo.use_count() << '\n'; return 0; // [deleter called] }

(3)赋值运算“=”

1) 复制:shared_ptr& operator= (const shared_ptr& x) noexcept; template <class U> shared_ptr& operator= (const shared_ptr<U>& x) noexcept;

2) 移动:shared_ptr& operator= (shared_ptr&& x) noexcept; template <class U> shared_ptr& operator= (shared_ptr<U>&& x) noexcept;

3) 从...移动:template <class U> shared_ptr& operator= (auto_ptr<U>&& x); template <class U, class D> shared_ptr& operator= (unique_ptr<U,D>&& x);

复制分配1) 将对象添加为 x 资产的共享所有者,从而增加它们的 use_count。移动分配 2) 将所有权从 x 转移到 shared_ptr 对象而不改变 use_count。 x 变成一个空的 shared_ptr(就像默认构造的一样)。同样,来自其他托管指针类型 3) 的移动分配也会转移所有权,并使用 set a use count of 1 进行初始化。

此外,在上述所有情况下,对该函数的调用与在其值更改之前调用了 shared_ptr 的析构函数具有相同的副作用(如果此 shared_ptr 是唯一的,则包括删除托管对象)。不能将指针的值直接分配给 shared_ptr 对象。您可以改用 make_shared 或成员重置。

托管指针类型的对象。U* 应隐式转换为 T*(其中 T 是 shared_ptr 的模板参数)。

用法举例:
#include <iostream> #include <memory> int main () { std::shared_ptr<int> foo; std::shared_ptr<int> bar (new int(10)); foo = bar; // copy bar = std::make_shared<int> (20); // move std::unique_ptr<int> unique (new int(30)); foo = std::move(unique); // move from unique_ptr std::cout << "*foo: " << *foo << '\n'; std::cout << "*bar: " << *bar << '\n'; return 0; }

(4)swap函数

函数声明:void swap (shared_ptr& x) noexcept; 参数x: 另一个相同类型的 shared_ptr 对象(即,具有相同的类模板参数 T)。作用是将 shared_ptr 对象的内容与 x 的内容交换,在它们之间转移任何托管对象的所有权,而不会破坏或改变两者的使用计数。

用法举例:
#include <iostream> #include <memory> int main () { std::shared_ptr<int> foo (new int(10)); std::shared_ptr<int> bar (new int(20)); foo.swap(bar); std::cout << "*foo: " << *foo << '\n'; std::cout << "*bar: " << *bar << '\n'; return 0; }

(5)reset函数

重置shared_ptr,对于声明1) 对象变为空(如同默认构造)。在所有其他情况下,shared_ptr 以使用计数为 1 获取 p 的所有权,并且 - 可选地 - 使用 del 和/或 alloc 作为删除器 和分配器。另外,调用这个函数有同样的副作用,就像在它的值改变之前调用了shared_ptr 的析构函数一样(包括删除托管对象,如果这个shared_ptr 是唯一的)。

1) void reset() noexcept;
2) template <class U> void reset (U* p);
3) template <class U, class D> void reset (U* p, D del);
4) template <class U, class D, class Alloc> void reset (U* p, D del, Alloc alloc);

用法举例:
#include <iostream> #include <memory> int main () { std::shared_ptr<int> sp; // empty sp.reset (new int); // takes ownership of pointer *sp=10; std::cout << *sp << '\n'; sp.reset (new int); // deletes managed object, acquires new pointer *sp=20; std::cout << *sp << '\n'; sp.reset(); // deletes managed object return 0; }

(6)get函数

函数声明:element_type* get() const noexcept; get()返回存储的指针。存储的指针指向shared_ptr对象解引用的对象,一般与其拥有的指针相同。存储的指针(即这个函数返回的指针)可能不是拥有的指针(即对象销毁时删除的指针)如果 shared_ptr 对象是别名(即,别名构造的对象及其副本)。

用法举例:
#include <iostream> #include <memory> int main () { int* p = new int (10); std::shared_ptr<int> a (p); if (a.get()==p) std::cout << "a and p point to the same location\n"; // three ways of accessing the same address: std::cout << *a.get() << "\n"; std::cout << *a << "\n"; std::cout << *p << "\n"; return 0; }

(7)取对象运算“*”

函数声明:element_type& operator*() const noexcept; 取消引用对象。返回对存储指针指向的对象的引用。等价于:*get()。如果shared_ptr的模板参数为void,则该成员函数是否定义取决于平台和编译器,以及它的返回类型 在这种情况下。

用法举例:
#include <iostream> #include <memory> int main() { std::shared_ptr<int> foo(new int); std::shared_ptr<int> bar(new int(100)); *foo = *bar * 2; std::cout << "foo: " << *foo << '\n'; std::cout << "bar: " << *bar << '\n'; return 0; }

(8)“->”操作符

函数声明:element_type* operator->() const noexcept; 取消引用对象成员。返回一个指向存储指针指向的对象的指针,以便访问其成员之一。如果存储的指针是空指针,则不应调用该成员函数,它返回与 get() 相同的值。

用法举例:

#include <iostream> #include <memory> struct C { int a; int b; }; int main() { std::shared_ptr<C> foo; std::shared_ptr<C> bar(new C); foo = bar; foo->a = 10; bar->b = 20; if (foo) std::cout << "foo: " << foo->a << ' ' << foo->b << '\n'; if (bar) std::cout << "bar: " << bar->a << ' ' << bar->b << '\n'; return 0; }

(9)use_count函数

函数声明:long int use_count() const noexcept; use_count 返回与此对象(包括它)在同一指针上共享所有权的 shared_ptr 对象的数量。如果这是一个空的 shared_ptr,则该函数返回零。库实现不需要保留任何特定所有者集的计数 ,因此调用此函数可能效率不高。 要具体检查 use_count 是否为 1,也可以使用 member unique 代替,这样可能更快。

(10)unique函数

函数声明:bool unique() const noexcept; 检查是否唯一 返回 shared_ptr 对象是否不与其他 shared_ptr 对象共享其指针的所有权(即,它是唯一的)。 空指针从来都不是唯一的(因为它们不拥有任何指针)。 如果唯一的 shared_ptr 对象释放此所有权,则它们负责删除其托管对象(请参阅析构函数)。 此函数应返回与 (use_count()==1) 相同的值,尽管它可能以更有效的方式执行此操作。 如果这是唯一的 shared_ptr,则返回值 true,否则返回 false。

用法举例:


#include <iostream> #include <memory> int main() { std::shared_ptr<int> foo; std::shared_ptr<int> bar(new int); std::cout << "foo unique?\n" << std::boolalpha; std::cout << "1: " << foo.unique() << '\n'; // false (empty) foo = bar; std::cout << "2: " << foo.unique() << '\n'; // false (shared with bar) bar = nullptr; std::cout << "3: " << foo.unique() << '\n'; // true return 0; }

(11)“bool”操作

函数声明:explicit operator bool() const noexcept; 检查是否为 null。 返回存储的指针是否为空指针。 存储的指针指向 shared_ptr 对象解除引用的对象,通常与其拥有的指针相同(销毁时删除的指针)。 如果 shared_ptr 对象是别名(即别名构造的对象及其副本),它们可能会有所不同。该函数返回的结果与 get()!=0 相同。 请注意,空的 shared_ptr(即此函数返回 false 的指针)不一定是空的 shared_ptr。 别名可能拥有某个指针但指向空,或者所有者组甚至可能拥有空指针(参见构造函数 4 和 5)。

用法举例:

#include <iostream> #include <memory> 
 int main() {     
std::shared_ptr<int> foo;    
 std::shared_ptr<int> bar(new int(34));      
if (foo) 
    std::cout << "foo points to " << *foo << '\n';     
else 
    std::cout << "foo is null\n";      
if (bar) 
    std::cout << "bar points to " << *bar << '\n';     
else std::cout << "bar is null\n";      return 0; }

(12)owner_before函数

函数声明:

template <class U> bool owner_before (const shared_ptr<U>& x) const;
template <class U> bool owner_before (const weak_ptr<U>& x) const;

基于所有者的排序。根据严格的弱基于所有者的顺序返回是否认为对象在 x 之前。与 operator< 重载不同,此排序考虑了 shared_ptr 的拥有指针,而不是存储的指针,使得两个 如果它们都共享所有权,或者它们都为空,即使它们存储的指针值不同,这些对象中的一个被认为是等效的(即,无论操作数的顺序如何,该函数都返回 false)。 如果 shared_ptr 对象是一个别名(别名构造的对象及其副本),则 shared_ptr 对象解引用)可能不是拥有的指针(即对象销毁时删除的指针)。该函数由 owner_less 调用以确定其结果。

用法举例:



#include <iostream> #include <memory> int main() { int* p = new int(10); std::shared_ptr<int> a(new int(20)); std::shared_ptr<int> b(a, p); // alias constructor std::cout << "comparing a and b...\n" << std::boolalpha; std::cout << "value-based: " << (!(a < b) && !(b < a)) << '\n'; std::cout << "owner-based: " << (!a.owner_before(b) && !b.owner_before(a)) << '\n'; delete p; return 0; }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1892344.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【 2024!深入了解 大语言模型(LLM)微调方法(总结)】

引言 众所周知&#xff0c;大语言模型(LLM)正在飞速发展&#xff0c;各行业都有了自己的大模型。其中&#xff0c;大模型微调技术在此过程中起到了非常关键的作用&#xff0c;它提升了模型的生成效率和适应性&#xff0c;使其能够在多样化的应用场景中发挥更大的价值。 那么&…

助力企业数字化转型:无锡哲讯——您的专业SAP系统运维服务商

数字化时代&#xff0c;企业对SAP系统的依赖程度日益加深&#xff0c;而SAP系统的稳定运行对于企业的核心业务至关重要。 无锡哲讯作为专业的SAP运维服务商&#xff0c;致力于为客户提供全面的SAP运维解决方案&#xff0c;助力企业实现数字化转型&#xff0c;提升核心竞争力。 …

再次登榜,深兰科技荣膺全球独角兽企业500强

6月27&#xff5e;28日&#xff0c;《2024全球独角兽企业500强》榜单发布&#xff0c;深兰科技凭借在AI产业赋能和产品出海方面的出色表现&#xff0c;继2023年之后再次登榜。 《2024全球独角兽企业500强》评委会介绍&#xff0c;本届榜单的产生&#xff0c;是由“全球独角兽企…

logback log.info耗时异常,RollingFileAppender+TimeBasedRollingPolicy配置踩坑

我喜欢把核心内容放开头 此次log.info耗时异常升高&#xff0c;是由于日志量过大&#xff08;5G甚至以上&#xff09;&#xff0c;并且使用同步阻塞的RollingFileAppenderTimeBasedRollingPolicy&#xff0c;导致log.info一直等待日志文件滚动&#xff0c;造成了异常。解决方式…

ETCD概述--使用/特性/架构/原理

ETCD概述 ETCD是一个高度一致的分布式键值存储, 它提供了一种可靠的方式来存储需要由分布式系统或机器集群访问的数据(高可用, 强一致性)​全局的配置服务中心. 本文将介绍其特性、相关操作和常见的应用场景. 如果想了解更多, 请查阅我的技术博客: https://dingyuqi.com 特性 …

专访ATFX首席战略官Drew Niv:以科技创新引领企业高速发展

在金融科技创新的浪潮中&#xff0c;人才是推动企业高速发展的核心驱动力&#xff0c;优质服务是引领企业急速前行的灯塔。作为差价合约领域的知名品牌&#xff0c;ATFX高度重视人才引进工作&#xff0c;秉持“聚天下英才而用之”的理念&#xff0c;在全球范围内广揽科技精英&a…

某业帮六月校招后端笔试

题目一 解题思路 签到题&#xff0c;dp就行。 题目二 解题思路 这个比较烦人&#xff0c;需要处理额外的引号和括号。用DFS&#xff0c;对于每个间隙&#xff0c;插入与不插入都搜一遍。 题目三 解题思路&#xff1a; 双指针&#xff0c;左右各一个指针&#xff0c;对比长度&…

变频器配置V20

变频器控制最好是变频电机&#xff0c;在速度不低的情况下工频电机 改变电机转速&#xff0c;调节扭矩&#xff0c; 变频器 L1 L2 L3 ,R S T 电机输入 uvw 电机输出 FSD 制动电阻 设置步骤 恢复出厂设置 p0010:30 p0970:21p0003:3(设定访问级别) P0003 用户访问级别 0 - 4 1…

沟通方法和技巧

0 Preface/Foreword 1 沟通对象 沟通维度&#xff1a; upward&#xff0c;向上沟通&#xff0c;直接上级downward&#xff0c;向下沟通&#xff0c;直接下级horizontal&#xff0c;横向沟通&#xff0c;同部门/跨部门同事 2 沟通方式&#xff08;5W2H&#xff09; 对于开会和…

css样式flex布局之,盒子垂直居中

<div class"item"><img src"../../assets/images!code_app.png" alt"" /><div>5555</div><p>微信扫一扫关注</p><p>“快速预约挂号”</p></div>.item{display: flex;flex-direction: col…

两个令人兴奋的 PostgreSQL 特性可改善 NULL 处理#PG认证

唯一列中的 NULL 值永久链接 一个众所周知但令人讨厌的怪异值NULL是NULL ! NULL&#xff0c;因此一UNIQUE列仍然可以有多个NULL值。 #PG培训#PG考试#postgresql培训#postgresql考试#postgresql认证 &#xff08;为了简单起见&#xff0c;示例使用数字 id 列&#xff0c;但我…

DevExpress WPF中文教程:Grid - 如何显示摘要(设计时)?

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

chrome 谷歌浏览器插件打包

1、找到id对应的字符串去搜索 C:\Users\<你的用户名>\AppData\Local\Google\Chrome\User Data\Default\Extensions2、选择根目录 直接加载下面的路径扩展可用&#xff1a;

liteide教程-运行平台和3264位

【1】编译后的运行平台选择&#xff08;linux/windows&#xff09; 1&#xff09;点击"查看"&#xff0c;选择"编辑当前环境" 2)GOOS参数修改 GOOSlinux //表示linux系统 GOOSwindows //表示Windows系统 【2】编译后的是32位还是64位程序 1&#x…

Word文档中公式的常用操作

一、参考资料 二、常用操作 插入公式 Alt 多行公式 Shift Enter 多行公式对齐 WORD Tips: 多行公式编辑及对齐 word自带公式等号对齐&#xff08;可任意符号处对齐&#xff09; 多行公式按照 为基准对齐。 拖动鼠标选中整个公式点击右键&#xff0c;选择【对齐点(…

使用表单系统快速搭建邀请和签到系统

在组织活动时&#xff0c;邀请和签到环节往往是活动成败的关键之一。传统的纸质邀请和签到方式不仅费时费力&#xff0c;还容易出现各种问题&#xff0c;例如名单遗漏、签到混乱等。而使用TDuckX“搭建邀请和签到系统”将彻底改变这一现状&#xff0c;为活动组织者提供了一种高…

半实物仿真测试系统

设备组成 test系统主要由硬件部分与软件部分组成。硬件部分由PCI机箱、PCI控制器以及各种PCI接口板卡组成。软件部分由测试设计软件模块、测试执行服务软件模块、测试执行客户端软件模块、设备资源管理软件模块等主要软件模块以及曲线数据生成、CRC插件生成与诊断、测试数据记录…

模块一SpringBoot(一)

maven记得配置本地路径和镜像 IJ搭建 SpringIntiallizer--》将https://start.spring.io改成https://start.aliyun.com/ 项目结构 Spring有默认配置&#xff0c; application.properties会覆盖默认信息&#xff1a; 如覆盖端口号server.port8888

项目管理:项目进度延迟怎么办?做好这三点就够了

项目管理中&#xff0c;项目进度延迟是项目经理常常面临的挑战之一。它不仅影响项目的按时完成&#xff0c;还可能对项目的整体质量、成本控制及客户满意度产生负面影响。 更为严重的是&#xff0c;当管理者对项目进度的实际情况一无所知&#xff0c;即出现“无法掌控”的局面…

翔云发票查验接口状态码说明,哪种情况扣次数那种情况不扣次数呢

翔云发票查验API&#xff0c;实时联网&#xff0c;可以实现发票信息真伪的快速核验&#xff0c;帮助企业财务摆脱繁琐的发票真伪查验工作。那么知道了发票查验接口的作用&#xff0c;对于开发者而言&#xff0c;接口返回的状态码又分别代表什么含义呢&#xff1f;下面就翔云发票…