Skip to content

C++_Pointers

ricojia edited this page Jun 26, 2022 · 21 revisions

========================================================================

raw pointers

========================================================================

Basics

  1. new and delete: new must be deleted, otherwise there will be a memory leak!!. see code

    • new
      • new will call default initialization (if we don't have args ofc).
    • new[]:size must be provided as part of the signature
      initialize an array using new: 
      new int[10]();
      new int[5] {1,2,3};  // without 5 wouldn't compile``
    • new[] must match delete[]. A simple delete will yield undefined behaviour.
      • delete should not be used on heap allocated objects
      • delete vs delete[]
        • delete will take not into account the number of objects, but delete[] will.
        • delete[] should not be used on regular char*, unless it is char* c = new char[size_]; .
  2. struct that contains ptrs, and we create struct using new

    • ptr's default initialization is nullptr,so no memory allocation is done. so we need to call new to create that memory
  3. Use Pointers

    1. Initialization
      • ptr in class won't be initalized to null_ptr - pointers do not get initialized automatically!! cuz ptr is a variable too. So you need to initialize them to nullptr explicitly.
    2. Not safe to get size of array using sizeof(int*)/sizeof(int)
    3. When to use raw pointers:
    • in small code where performance is crucial, and ownership has no confusion about it.
    • If your function doesn't use the lifetime semantics of the smartpointer, use a raw pointer, or a reference. You don't pay for what you don't need!.
      //good example: 
      void f(widget& w){w = 1; }
      //bad: 
      void f(shared_ptr<widget>& w){*(w.get())=3;}
      // because if another shared_ptr may also be pointing to the same thing (which you don't know), and you do w.reset(new something ()), you do not modify that pointer's object. 
    1. After freeing/deleting a pointer, always set it to nullptr
      free(ptr_);
      ...
      realloc(ptr_, size);    // segfault, since ptr is not null but its memory has been freed.
      
      delete ptr_ ; 
      ptr_ = nullptr      // this will not automatically set ptr_ to null!
      • Reason can delete rvalue. But = wouldn't work with that.
    2. Cautions
      • Always initialize your pointer!
  4. Drawbacks: int* i

    • Array?
    • should delete this? Ownership?
      • If yes, how? thru a dedicated destruction function, or delete? If delete, is it delete[] or delete?
    • Dangling pointer?
    • missing one of the poitner's occurances?
  5. int* const is a const ptr, const* int is ptr to const

    • const shared_ptr is equivalent to T* const, where p itself is const, not the pointee.
    • (also if you want pointer to const, do shared_ptr).
      • must be initialized in initializer_list!

Array

  1. arr[1] = 10 will not throw error even if arr doesn't own the array.

    • but may throw segfault when reading arr[1]
  2. arr[1][2] array requires arr to be int**, not just int.

  3. No easy way to convert pointer back to array

Function Pointers

  1. Basic Example:

    // int pf(int) is actually function pointer
    // int (pf)(int)
    // int (*pf)(int) are all valid
    void f (int pf(int)){
        pf(1); 
    }   
    
    f(some_func); 
  2. Function overloading with func pointers: templated functions pointer can be casted, too.

    // Function overloading with func pointers =========================
    // int pf(int) is actually function pointer
    void f2 (int pf(int)){}   //function trying to forward a function pointer. 
    int processVal(int){return 0;}; 
    int processVal(int, int){return 0;};      // We have two overloaded functions
    
    //Tricky
    template <typename T>
    int someFunc(T param){
        return 0;
    }
    
    using FuncType = int (*)(int);
    
    void forward_func_ptr_works(){
        f2(processVal);    // fine, because function signature is clear
        // f2(someFunc);      //Error: You need to specify the signature of someFunc!
        f2(static_cast<FuncType>(someFunc));     
    }

Other functionalities for pointers

  1. std::nothrow: disable exceptions during new or new[], and get nullptr for return instead.

    #include <new>
    while(1){
        int *ptr = new (std::nothrow) int[100000000ul]; // This will fail
        if (ptr == nullptr) cout<<"failed to allocate memory"<<endl;    
    }
  2. placement new

    1. pros:
      • when memory is limited, there might be memory failure on heap. Placement new allows us to allocate memory on stack
      • Faster pointer memory construction, and we know the memory address
      • Easier memory management (don't need to free that memory yourself), also delete cannot free stack memory
    2. Peligros: placement new will yield segfault: the pointer passed in is
      1. void pointer
      2. nullptr
      3. not pointing to anything
    3. example
      // Basic syntax: can specify address
      // new (address) (type) initializer
      
      #include <iostream>
      #include <string>
      using namespace std;
      int main()
      {
          //example 1
          char *buf  = new char[sizeof(string)]; // pre-allocated buffer
          string *p = new (buf) string("hi");    // placement new, takes less time to construct
          string *q = new string("hi");          // ordinary heap allocation
      
          // Example 2
          while(1){
              int *ptr = new (std::nothrow) int[100000000ul]; // This will fail silently
              char alt_arr[100000000ul * sizeof(int)];
              int* ptr2 = new(alt_arr) int(5);      //put a single 5 in the slot
          }
          return 0;
      }
  3. std::experiment::propagate_const (c++14): treats a weapped pointer as a pointer to const, when the upstream caller is const.

    #include <experimental/propagate_const>
    #include <iostream>
    #include <memory>
    
    using std::cout; using std::endl; 
    
    struct X{
      void g(){cout<<"non const g"<<endl; }
      void g() const {cout<<"const g"<<endl; }
    };
    
    struct Y{
      Y(): m_ptrX (std::make_unique<X>()){}
      void f(){
        cout<<"non const f"<<endl; 
        m_ptrX->g();
      }
      void f() const {
        cout<<"const f"<<endl; 
        m_ptrX->g();
        }
      std::experimental::propagate_const<std::unique_ptr<X>> m_ptrX; 
    };
    
    int main(){
      Y y; 
      y.f();    // see "non const g()"; 
      const Y cy; 
      cy.f();   // see "const ()"
    }

========================================================================

Smart Pointers Summary

========================================================================

  1. Motivation: a scoping object that can hold RAII object, where resources are allocated on heap. C++ is not a garbage collecting language, this is important.

    • raw pointers are encapuslated by a smart pointer, so the smart pointer is responsible for the raw pointer.
    • The purpose of smart ptrs is to prevent memory leaks, and to gurantee the liveliness of an object pointed to by multiple pointers. Also, we do not need to explicit delete a smart ptr.
  2. Short Summary:

    • auto_ptr is C++ 98's unique_ptr
    • after move std::move, an unique_ptr will be nullptr.
    • can do unique_ptr -> shared_ptr, but not shared_ptr -> unique_ptr
  3. Common things to notice

    1. to initialize smart pointer reference:
      • for const ptr&, you can use nullptr, for ptr&, you can't
    2. Initialization (shared ptr, unique_ptr)
      • swap: see code
        std::unique_ptr<int> ptr; 
        ptr.swap(ptr2); 
        
        std::shared_ptr<LargeObject> pLarge(nullptr);
        pNewLarge.swap(pLarge);
        
        // reminder 
        // But this can cause crash, because this is not default ctor, it's just nullptr
        auto default_ptr = std::unique_ptr<crash_t>();
        // This is the real default ctor using unique_ptr
        default_ptr = std::unique_ptr<crash_t>(new crash_t());
    3. reset
      // can be used to destruct
      unique_ptr.reset(); 
      
      // but in shared ptr reset is just resetting the ptr itself, and --count
      shared_ptr.reset()
      
      // or shared_ptr can do 
      shared_ptr = nullptr
    4. Conversion: no shared_ptr -> unique_ptr, but you can do unique_ptr -> shared_ptr
    5. get()
      unique_ptr.get(); 
      shared_ptr.get();
  4. Differences

    1. Working with arrays: shared_ptr doesn't work with C arrays, use std::vector instead. unique_ptr can

      std::unique_ptr<int []> unique_ptr; 
    2. You can pass raw ptr into unique_ptr, Never pass a raw pointer into shared_ptr:

      • The raw ptr might be owned by multiple shared_ptr.
      • So only the first one destructing it can delete it successfully. Otherones will give undefined errors.
      • only time when you have to use a raw ptr is when you have custom deleter. Then, use new
    3. Initialization

      • ofc shared ptr can do but unique_ptr can't do
        auto pNewLarge(pLarge);
        pNewLarge = pLarge;
    4. Destruction

      • shared_ptr is more flexible as it doesn't require custom deleter as part of its type.
        #include <memory>
        auto customDeleter1 = [](Foo* f){delete f}; 
        auto customDeleter2 = [](Foo* f){delete f}; 
        std::shared_ptr<Foo> ptr1 (new Foo, customDeleter1); 
        std::shared_ptr<Foo> ptr2 (new Foo, customDeleter2); 
        
        //This allows you to put these in a more generic vector: 
        std::vector< std::shared_ptr<Foo> >{ptr1, ptr2}; 
    5. shared_ptr see Count references pLarge.use_count();

========================================================================

Unique Pointer

========================================================================

Basics

  1. Motivation

    • Ownership can be only one. Can be moved to another object, but not by copying
    • std::unique_pointer is smaller and more efficient than boost::scoped_ptr. The size is only one raw pointer
  2. Basics

    • Internally has a pointer pointing to heap memory, then delete it using delete.
    • destruct the object by reset()
    • return ptr and gives up ownership: release()
  3. Create a std::unique_ptr

    • Summary

      #include<memory>
      // 1. make unique
      std::unique_ptr<LargeObject> pLarge = make_unique<LargeObject> ();
      // or using copy ctor: 
      pLarge2 = make_unique<LargeObject>(*pLarge); 
      
      // 2. create using reset.
      std::unique_ptr<LargeObject> ptr;
      ptr.reset(new LargeObject());
      
      // 3. very convenient! - wrapping around a new pointer, but may not be exception safe
      using Ptr = std::unique_ptr<int>;
      auto i = new int(1);
      Ptr ptr(i);
      
      // Note: in ctor you can 
      Foo(int &i):j(new int(i)){          }
      //But you can't do
      j = new int(i);
      //Because IMPLICIT CONVERSION RAW POINTER -> SMART POINTER might cause problems.
      unique_ptr = some_ptr;    // not working, have to use reset.
      
      // array 
      std::unique_ptr<unsigned char[]> testData(new unsigned char[16000]());
    • Make_unique (C++14)

      • make_unique can is doing std::forward() on args, for non-array type.
      • For array types, you should specify size (TODO)
      • Make_unique uses template, whose type will be deduced. So, if you use initializer list, it won't be deduced
        std::make_unique<std::vector<int>>({1,2,3});		//Bad: Initializer list will not be deduced
        std::make_unique<std::vector<int>>(std::vector<int>{1,2,3});		//Good: Initializer list will not be deduced
    • Suicidal

      int main(){
          auto ptr1 = std::unique_ptr<int>(new int(1));
          if (ptr1) cout<<"ptr1"<<endl;
          {
              // freed ptr1's resource, though after this ptr1 still not null
              auto ptr2 = std::unique_ptr<int>(ptr1.get());
          }
          if (ptr1) cout<<*ptr1<<endl;
      
      }
  4. Other Basic Operations

    1. Get the raw pointer pLarge.get();
    2. Ease to convert to shared_ptr: //thru move semantics, or copy elision
      std::unique_ptr<int> arr_ptr (new int{1});
      std::shared_ptr<int> ptr2 = std::move(arr_ptr);
      • This can be useful in Factory method, as the factory function can allow the pointer being used either as unique_ptr, or shared_ptr in the future
      • Can be converted to std::shared_ptr<void>
        std::unique_ptr<int> u_ptr (new int{1});
        std::shared_ptr<void> ptr = u_ptr;
  5. Destruction

    1. if unique_ptr.get() == nullptr, the destructor has no effect.
    2. but if you have custom deleter, you have to call delete on the pointer!
  6. ref to object created by unique_ptr, what if unique_ptr is nullptr? How to check the reference? using &.

    #include <memory>
    #include <iostream>
    using std::cout;
    using std::endl;
    
    void foo(int & i){
        if (&i == nullptr) cout<<"ha"<<endl;
        else cout<<"he"<<endl;
    }
    int main(){
        std::unique_ptr<int> i;
        foo(*i);
    }

Uses

  1. Typical use is factory method.
    class Investment;
    class Stocks: public Investment;
    class Bonds: public Investment;
    template <typename T>
    std::unique_ptr<Investment> makeInvestment(T&& args);    //Factory function that returns a ptr to investment, on demand
  2. Pass unique pointer into/out of function
    • Transfer ownership, by std::move (for lvalues) or rvalues (copy ellision)
      void Foo(std::unique_ptr<Bar> bar_ptr);
      Foo(std::unique_ptr<Bar>(new Bar()));
      //OR
      std::unique_ptr<Bar> bar_ptr_(new Bar());
      Foo(std::move(bar_ptr_));	//move semantics for lvalues.
    • polymorphism using unique_ptr
      class Base{...};
      class derived{...};
      std::unique_ptr<Base> Func(std::unique_ptr<Base> base){
          std::unique_ptr<Derived> derived_ptr(new Derived());
      }

Cautions

  1. Except for copy elision, we cannot copy a unique_ptr, because of unique_ptr(const unique_ptr&) = delete;. So we have to meet all requirements for copy ellision

    • why wouldn't this work?
      #include <memory>
      #include <utility>
      using Ptr = std::unique_ptr<int, void (*)(int*)>;
      Ptr foo(){
          Ptr ptr1 (new int(100), [](int* p){delete p;});
          // return Ptr (nullptr, nullptr);       // 1: this is fine
          // return ptr1;                         // 2: this is fine too
          return (1==1)? ptr1 : Ptr (nullptr, nullptr);  // 3: this will pop up an Error "error: use of deleted function 'std::unique_ptr<>..." Why?
          // unique_ptr(const unique_ptr&) = delete; But 1 works because of Return-Value-Optimization, an optimization that uses copy-elision.
          // Copy-elision allows the caller function directly get the local variable being returned.
          // However, copy-elision is disabled if which object to return is decided during run time, specifically, the ternary statement in 3.
          // A fix is simple: either not use ternary, or use std::move to move_construct the returned object (which in many cases is a swap of pointers)
          // return (1==1)? std::move(ptr1) : Ptr (nullptr, nullptr);
      }
      int main()
      {
          auto ptr = foo();
          return 0;
      }
  2. use unique_ptr reference?

    • If you have lambda expression like [&](){ptr...}, sure go for it.
    • don't use pointer to a smart pointer. That totally loses its purpose.
    • don't use reference to smart pointer either. Bad.
  3. unique_ptr is meant to replace new, delete.

    • so it doesn't work with ptrs to stack allocated objects. Otherwise if unique_ptr takes in a pointer not created by new, there'd be double-freeing issues.
  4. Clearly distinguish pointer to a single obj, or to an array

    std::unique_ptr<int[]> arr_ptr (new int[10] {1,2,3});
    cout<<arr_ptr[2];
  5. unique_ptr: wouldn't make sense to use unique_ptr, as it is not gonna be freed.

std::unique_ptr with custom deleter

  1. C++ unique_ptr signature allows a custom deleter (second template argument):

    template<class T, class Deleter=std::default_delete<T>>
    class unique_ptr;
    
    //So a unique_ptr is actually:
    | raw_ptr | deleter |
  2. Custom Deleter: a custom deleter could be a functor, a lambda, or a function pointer

    struct CustomDeleter{
      void operator()(Obj* obj)const{     //Define a functor here.
          cout<<"HAHAHA"<<endl;
          delete obj;
        }
    }
    
    int main(){
    Obj* obj_ptr = nullptr; // or some function that returns a raw pointer
    
    // Method 1: empty (no-data members) functor:
    auto rp = std::unique_ptr<Obj, CustomDeleter>(obj_ptr, CustomDeleter());     
    
    // Method 2: capture-less lambda, also smallest implementation, need decltype, like in priority_queue
    auto lam = [](Obj* p){ delete p; };
    auto rp2 = std::unique_ptr<Obj, decltype(lam)>(obj_ptr, lam);     
    
    // Method 3: function pointer, 16 bytes for that. slightly larger
    void func(obj*);
    std::unique_ptr<Obj, void(*)(int*)>;    //or decltype(&func)
    
    // Method 4: std::function, which is MUCH LARGER
    std::unique_ptr<obj, std::function<void(int*)>>;
    }
  3. Restrictions

    1. you can't make unique_ptr directly a nullptr, you have to pass it in.
      std::unique_ptr<AVFrame, void (*)(AVFrame*)> output_frame = unique_frame(nullptr, nullptr)
    2. reset only needs ptr part, not the deleter part.
      std::unique_ptr<Foo, D> up(new Foo(), D());  // up owns the Foo pointer (deleter D)
      up.reset(new Foo());  // calls deleter for the old one
  4. Change deleter: need deleter to be of the same time. Then can use =, move, get_deleter()

    1. you can do move:
      IntUniquePtr p1(new int(42), deleteEvenNumber);
      IntUniquePtr p2(new int(43), deleteOddNumber);
      p1 = move(p2);
    2. You can manually change the deleter, as .get_deleter() will return the deleter as non const reference, so you can change that
      p1.get_deleter() = deleteOddNumber;
    3. **Neatest: use = **
      IntUniquePtr p1(new int(42), deleteEvenNumber);
      p1 = IntUniquePtr(new int(43), deleteOddNumber);

========================================================================

Shared_pointer

========================================================================

Basics

  1. Motivation

    • ownership can be multiple. The raw pointer won't be freed until the last owner is out of scope.
  2. Basics

    1. make shared pointers
      #include <memory>
      std::shared_ptr<LargeObject> pLarge = make_shared<LargeObject>();  
      //not recommend std::shared_ptr<LargeObject> pLarge(new LargeObject),since it's slower and not exception safe.
      // Because you first allocate memory for the control block, then allocate memory for the object. 
      // If you do make_shared, you will allocate the two in one call, which reduces construction overhead. 
      std::shared_ptr<LargeObject>pLarge(nullptr);      
    2. frees the object pointed to by the pointer, delete a pointer.
      pLarge.reset(); //sets the pointer to null, minus the reference count by 1.  
      pLarge = nullptr;   //sets the pointer to null. 
      pLarge = make_shared<LargeObject>();    //frees the object, and point to a new object. 
    3. Shared_ptr reset() and use_count()
      #include<iostream>
      #include<memory>
      using std::cout; using std::endl;
      int main(){
          std::shared_ptr<int> pt = std::make_shared<int>(3);
          cout<<pt.use_count()<<endl; // see 1. 
          std::shared_ptr<int> pt2 = pt;
          cout<<pt.use_count()<<endl; // see 2. 
          cout<<pt2.use_count()<<endl; // see 2. 
          pt.reset(); 
          cout<<pt.use_count()<<endl; // see 0. 
          cout<<pt2.use_count()<<endl; // see 1. 
          auto pt3 = pt2.get(); 
          cout<<pt2.use_count()<<endl; // see 1; 
          // pt2.get() will return a raw pointer, and the shared_ptr will not change the ref count. 
          return 0;
      }
  3. Uses

    1. static_pointer_cast vs dynamic_pointer_cast:

      • RTTI is run time type information, if a base class has at least a virtual function, then base class ptr can be dynamic_cast to derived class (downcast, upcast is derived -> base)
      • static_cast happens during compile time, no RTTI is needed. dynamic_cast is only for downcasting. dynamic_cast can happen in runtime. If downcasting fails (not derived class), nullptr is returned. If you're sure you can downcast, use static_cast since it's cheaper, and the language allows you to do so
      • static_pointer_cast vs static_cast: static_pointer_cast works on shared_ptrs, because you can't cast its type directly.
        #include <iostream>
        using namespace std;
        class B {
            //virtual void fun() {} // NEED TO ADD THIS!!
        };
        class D : public B {
        };
        
        int main()
        {
            B* b = new D;
            D* d = dynamic_cast<D*>(b);
        
            // 1. use dynamic_cast if we're not sure if we can succeed
            if (d != NULL)
                cout << "works";
            else
                cout << "cannot cast B* to D*";
        
           // 2. cpp still allows you to use static_ptr_cast
           std::static_pointer_cast<DerivedClass>(ptr_to_base)->f();
           // 3. even static_pointer_cast
           static_cast<DerivedClass*>(ptr_to_base.get())->f();      // equivalent to above
        
            return 0;
        }
    2. std::shared_ptr<void> can be used as a replacement for void*, which can store any type of raw pointer, or unique_ptr!!!

      std::shared_ptr<void> ptr (new Foo());          //creating a new Foo ptr
      ptr.get();        // return a std::shared_ptr<void>
      *(std::static_pointer_cast<int>(ptr));        //This is how you use it, Note that we're using std::static_pointer_cast
      • Cool thing: ptr can call Foo type deleter, which means we don't need to worry about deletion! YAYYY even tho ptr itself is void
      • Cuz ptr stores a custom deleter during initialization. Whether the actual type is a child class, or in this case Foo, their default deleter is passed in here.
      • Therefore, even tho .get() will return a shared_ptr<void>, the actual deleter is the default
  4. Cautions:

    1. So you can use std::enable_shared_from_this to address this specific issue
      • if you pass "this" pointer to an external shared_ptr, be careful**
        class Foo{
          void processFoo(std::vector<std::unique_ptr<Foo>> processed&){
              ...
              // Here this is implicitly converted into a new std::shared_ptr, whose control block
              //has nothing to do with this object
              processed.push_back(this);  
              //So the external processed vector might delete this object AFTER it gets destructed
            }
        }
      • std::enable_shared_from_this is called a "base class template". Internally, enable_shared_from_this will share the same control block with an existing shared_pointer. So, you need to make sure at least one shared_ptr has been created, which could be done by Factory method
        class Foo : public std::enable_shared_from_this<Foo>{
          public: 
            Foo& createFoo(); 
            static void processFoo(std::vector<std::unique_ptr<Foo>> processed&){
                processed.emplace_back(shared_from_this());       // Built in enable_shared_from_this function
              }; 
          private: 
            //ctors
        }
    2. no [] or ptr arithmatic on shared pointers. so use a raw pointer from the shared ptr

Theory

  1. size is two pointers, one is for the raw pointer, one is for the ptr control block.

    • shared_ptr also has a virtual function that makes sure the right derived class is deleted
    • control block: a reference counter: a copy of custom deleter, and a copy of an allocator.
      • constructed when a new object is created
  2. One key thing about shared_ptr is reference count: stored in heap memory (i.e control block of the object, see below), incrememented only by copy operations.

    • Things will create a ref count (as well as a control block)
      • make_shared(creating a new object)
      • unique_ptr
    • Things will change ref count:
      • using a raw ptr
      • from another shared_ptr
    • Things won't increment the ref count:
      • move assignment / construction
    • Ref must be changed atomically, so shared_ptr is a bit slower.
  3. Prefer std::make_shared to new

    1. std::make_shared perfect forward arguments to the object's dynamic constructor
    2. Advantage 1: in make_shared, new and ptr storage happens atomically, while in new, they're two separate steps, which in some cases can lead to memory leaks. So, don't use new, unless you must do custom deletion.
      • Example: say we have this function
        // In runtime, ```some_small_func()``` can happen anytime. This is because the compiler doesn't generate code to make sure about that order. 
        // here the shared_ptr creates memory and stores the raw ptr atomically
        void processFoo(std::shared_ptr<Foo> ptr, some_small_func());  
        
        // If we use ```new```, ```some_small_func()``` may get executed between ```new``` and constructing ```std::shared_ptr<Foo>```.  If ```some_small_func()``` fails, the memory created by ```new``` won't be claimed back
        processFoo(std::shared_ptr<Foo> (new spw), some_small_func());  // here some_small_func is computed between creating the new pointer, and storing it with the shared_ptr!
    3. Advantage 2: make_shared has the memory and the control block in the same place, so one memory call is fine, which decreases the static size of the program
      • Speed might be faster as we allocate memory only once
    4. Disadvange 1: cannot have custom deleter
    5. Disadvange 2: might have a delay between the destruction of shared_ptr destruction of object
      • This is because the memory block, for the control block and the object, are stored together.
      • So the memory it occupies cannot be released, just because the object is destructed,
        • That's because: weak pointer might also be pointing to the obj.
        • Weak count will count how many weak_ptrs there are, but weak count will use the primary count.
    6. **Disadvange 3 ** Do not take in braces (we've seen that, because make_shared takes in template argument, and {} is not type deducible)
    std::make_shared<std::vector<int>>({1,2,3});    //illegal

========================================================================

Weak_pointer

========================================================================

Basics

  1. Motivation:

    • This can be used to break the circular reference between two shared_ptr

        class Foo{
          std::shared_ptr<Bar> ptr;
        }
      
        class Bar{
            //Option 1
            std::shared_ptr<Foo> ptr;     //this is circular, neither object will be deleted;
            // Option 2
            Foo* ptr_2;       // This doens't tell you the liveliness of Foo
            // Option 3
            std::weak_ptr<Foo> ptr_3;     // This will not try to delete A, so won't cause circular issue.
          }
    • Can access an object owned by shared_ptrs. serving as an augmentation of shared_ptr

    • But this does not participate in ownership, so no lifetime managing, hence nothing interferes with a reference count.

    • Applications where you don't want to manage the livetime of tthe obeject, but you do care about their liveliness, such asCache or the observer pattern

        #include <memory>
        std::shared_ptr<Foo> returnCachedPtr(id){
            auto obj_ptr = cache[id].lcok();    // here cache can be std::unordered_map.
            if (obj_ptr != nullptr){
                obj_ptr = load_Object();
              }
            return obj_ptr;
          }
  2. Basics

    • no guarantee the referenced object is still alive. (Dangling pointer)
    • But you can check it, by reading the exising shared_ptr's weak reference!!!
    • created by shared_ptr and can return a shared_ptr, or null (if the pointee is not alive)
    • Cannot be dereferenced directly
    • E.g
        std::shared_ptr<Foo> ptr1 = std::make_shared<Foo>(args);
        std::weak_ptr<Foo> ptr2(ptr);     //create like this.
      
        //checking liveliness of the pointee, only
        if (ptr2.expired())
      
        // Checks the liveliness and Return a shared_ptr ATOMICALLY
        std::shared_ptr<Foo> ptr3 = ptr.lock();
      
        // Returns a shared_ptr and throws a std::bad_weak_ptr error:
        std::shared_ptr<Foo> ptr4(ptr2);
Clone this wiki locally