5 Ağustos 2019 Pazartesi

std::shared_ptr Sınıfı

Giriş
Şu satırı dahil ederiz.
#include <memory>
Lock Yöntemi
Reference counting ile birden fazla göstergecin aynı anda tek bir nesneyi kullanması sağlanır.
Reference counting'in lock ile yapılıp yapılmadığı anlaşılabilir. Visual Studio 2012'de her zaman false döner.
#include <atomic>
#include <cstdio>
#include <memory>

int main() {
    auto ptr = std::make_shared<int>(0);
    bool is_lockless = std::atomic_is_lock_free(&ptr);
    printf("shared_ptr is lockless: %d\n", is_lockless);
}
Eğer tek thread'li bir ortamda çalışıyorsak lock policy single olacağı için true döner.
template<typename T>
using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;
Kalıtım
Bu sınıf Coercion by Member Template yöntemi ile kalıtım kullanan tiplerin birbirlerine atanabilmesini sağlar.

void Tip
Şu kod derlenir.
struct Thing{
  ~Thing(){
    cout<<"Destroyed\n";
  }
  int data;
};

int main(){
  {
    shared_ptr<void> voidPtr;
    {
      shared_ptr<Thing> thingPtr = make_shared<Thing>();
      voidPtr = thingPtr;
    }
    cout<<"thingPtr is dead\n";
  }
  cout<<"voidPtr is dead\n";
  return 0;
}
Çıktı olarak şunu alırız
thingPtr is dead
Destroyed
voidPtr is dead
Metodlar
Bu sınıfın tam 13 tane constructor metodu var.

Constructor metodu - raw pointer
Şöyle yaparız.
shared_ptr<Type> var(new Type());
1. Constructor explicit olduğu için şu kod derlenmez. Yani raw pointer, shared_tipine çevrilemez.
shared_ptr<Type> var = new Type();
Eğer bu işleme müsaade edilseydi şu tür kodlar hataya sebep olurdu.
void foo(std::shared_ptr<int> bar) { /*do something, doesn't matter what*/ }

int main()
{
    int * bar = new int(10);
    foo(bar);
    std::cout << *bar;
}
Bu da hataya sebep olurdu.
void f(std::shared_ptr<int> arg);
int a;
f(&a); // bug
2. Aynı nesneye iki farklı pointer kullanmak yanlıştır.
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
Constructor - delete
shared_ptr, contructor içine verilen nesnenin tipini delete işlemi için kullanır. Örnekte template parametresi olarak void verilse bile kod doğru çalışır.
shared_ptr<void> exampleVoidCons(new MyClass);    
Constructor - default_delete - arrray içindir
C++11
Sınıfı nesneleri delete ile siler. Eğer array kullanıyorsak delete[] şeklinde silmek isteriz bunun için std::default_delete metodunu kullanırız. std::default_delete içine tip + [] işareti alır
Örnek - int []
Şöyle yaparız.
std::shared_ptr<int> ptr(new int[5]{1,2,3,4}, std::default_delete<int[]>());
Örnek - char []
Şöyle yaparız
std::shared_ptr<char> ptr(new char[size], std::default_delete<char[]>());
Örnek - unsigned char []
Şöyle yaparız.
std::shared_ptr<unsigned char> ptr(
        new unsigned char[10],
        std::default_delete<unsigned char[]>()));
C++17
Sınıfı array'leri de destekler. Şöyle yaparız.
shared_ptr<unsigned char[]> ptr (new unsigned char[N])
Constructor - custom deleter
Metodun imzası şöyle
template< class Y, class Deleter, class Alloc > 
std::shared_ptr( Y* ptr, Deleter d, Alloc alloc );
Örnek
Şöyle yaparız. Dosya functor ile otomatik kapatılır. Functor yerine fclose() metodu da geçilebilir. Sadece örnek olması için functor kullanıldı.
struct FILE_deleter {
  void operator() (FILE* fp) const {
    fclose(fp);
  }
};

void f()
{
  std::shared_ptr<FILE> sp(fopen("some-file.txt"), FILE_deleter{});
    // Now do whatever you want with sp: make copies of it, pass it around, whatever.
}
Örnek
Custom deleter içinde shared_ptr artık silindi sayılsa daha iyi. Şöyle yapmak doğru olmayabilir.
struct Foo : public std::enable_shared_from_this<Foo>
{};

void deleter(Foo *f)
{
  {
    std::shared_ptr<Foo> tmp = f->shared_from_this(); // Line A
  }
  delete f;
}

int main()
{
  std::shared_ptr<Foo> foo(new Foo, &deleter);
}
Constructor - unique_ptr
Metodun imzası şöyle
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r );
unique_ptr'nin shared_ptr'ye dönüşebilmesini sağlar.

Örnek - aynı template parametresi
Şöyle yaparız. Eğer gerekiyorsa unique_ptr std::move() ile r-value haline getirilir.
shared_ptr<int> getInt2() {
    return make_unique<int>(2);
}
Örnek - farklı template parametresi
Açıklaması şöyle
This constructor shall not participate in overload resolution unless unique_ptr<Y, D>::pointer is convertible to T*.
Şöyle yaparız.
auto up = std::unique_ptr<int[]>(new int[5]{});
auto sp = std::shared_ptr<int>(std::move(up));
Ya da şöyle yaparız.
auto sp = std::shared_ptr<int>(up.release(), up.get_deleter());
Contructor - aliasing
Aliasing Constructor yazısına taşıdım.

Constructor - std::make_shared
std::make_shared yazısına taşıdım

get metodu
shared_ptr sınıfı sakladığı raw pointer'a get() metodu ile erişilebilmeyi sağlar. Örnek:
std::vector<int*> vector2;
vector2.push_back(std::make_shared<int>(4).get());
operator []
Eğer shared_ptr bir diziye işaret ediyorsa, dizideki belirtilen elemana erişmek için kullanılır.

operator-> ve operator*
Her iki operator de shared_ptr'nin sakladığı raw pointer null olsa bile exception atmazlar.

reset metodu
İmzası şöyle.
void shared_ptr::reset() noexcept;
Örnek
Şöyle yaparız.
shared_ptr<Type> var;
var.reset(new Type());
reset metodu - Y
İmzası şöyle.
template <typename Y>
void shared_ptr::reset(Y* ptr);
use_count metodu
Metodun imzası şöyle. İlginç bir şekilde long tipi dönüyor.
long use_count() const noexcept;
Şöyle yaparız.
std::shared_ptr<int> s1 = std::make_shared<int>(0);
std::cout<<s1.use_count()<<std::endl;
Cast Metodları
std::static_pointer_cast
Aynı nesneye işaret eden iki shared_ptr oluşturur. static_cast ile aynıdır. Aşağıdaki örnekte MyClass static_cast ile void'e cast edilse bile sorun olmaz.
shared_ptr<MyClass> example(new MyClass); 
shared_ptr<void> castToVoid = static_pointer_cast<void>(example);

std::dynamic_pointer_cast
Aynı nesneye işaret eden iki shared_ptr oluşturur. dynamic_cast ile aynıdır. Yani cast işlemi başarısız ile null içeren bir shared_ptr döner. Base sınıfın en az 1 tane virtual metodunun olması gerekir.

Ben çoklu kalıtım varsa kullanılması taraftarıyım. Yoksa static_pointer_cast yeterli.
std::shared_ptr<Base> base (new Derived());
std::shared_ptr<Derived> derived = std::dynamic_pointer_cast<Derived> (base);
Object Pool
ObjectPool sınıfını şöyle yaparız.

Bu sınıf ilk başlarken havuzu doldurmuyor. İstek geldikçe havuz boş ise yeni bir tane nesne yaratıyor. Belki yeni bir constructor ile havuzun kaç nesne ile başlaması gerektiği belirtilebilir.

İlâveten bu sınıf thread safe değil. Belki başka bir sınıf ile sarmalanarak, thread safe yapılabilir. Bu yeni sınıfı ObjectPoolMT olarak adlandırabiliriz.
class ObjPool<T>
{
private:
  std::vector<T*> freeObjPool;
public:
    
  void destroy(T *o)
  {
    freeObjPool.push_back(o);
  }

  std::shared_ptr<T> get()
  {
    T* o;
    if (freeObjPool.empty())
      o = new T();
    else
    {
      o = freeObjPool.back();
      freeObjPool.pop_back();
    }
    return std::shared_ptr<T>(o, 
             std::bind(&ObjRecycler::destroy, this, std::placeholders::_1));  }
}


Hiç yorum yok:

Yorum Gönder