24 Ağustos 2020 Pazartesi

Placement New

Giriş
Şu satırı dahil ederiz. Placement New "operator new" için overload edilmiş hallerinden birisi
#include <new>
Wikipedia'daki Placement Syntax bu yazıdaki hemen her şeyi kapsıyor. Ancak ben daha kısa açıklamak istedim.
Heap'ten bellek almak yerine, programcının sağladığı alanı kullanarak nesne yaratılır. Yani mevcut bir belleği kullanarak nesne yaratma anlamına geliyor. Yine null döndüren new kullanımında olduğu gibi new çağrısının içine parametre geçmek gerekiyor. Bu parametre programcının sağladığı bellektir.
Söz dizimi şöyle
Foo* foo = new(bellek alanı) Foo();
1. Kullanım
Örnek
Şöyle yaparız.
int arr[5];
ClassA* a = new(arr) ClassA();
Bazı örneklerde bellek void* şeklinde geçiliyor, ancak böyle yapılmasa da ben bir fark göremedim.
new (reinterpret_cast<void *>(p)) T(val);
Örnek
cast etmeye gerek yok. Şu kod yanlış.
new ((T*)object_) T;
Örnek
Belleğini kendi içinde taşıyan bir nesne için şöyle yaparız.
template<typename T>
class StaticObject
{
public:
  StaticObject() : constructed_(false) {}

  ~StaticObject()
  {
    if (constructed_)
      ((T*)object_)->~T();
  }

  void construct()
  {
    assert(!constructed_);

    new (object_) T;
    constructed_ = true;
  }

  T& operator*()
  {
    assert(constructed_);

    return *(std::launder((T*)object_));  }

  const T& operator*() const
  {
    assert(constructed_);

    return *((T*)object_);
  }

private:
  bool constructed_;
  alignas(alignof(T)) char object_[sizeof(T)];
};
2. Alignment
Eğer aynı buffer içine birden fazla nesne yerleştirilecekse alignment'a dikkat etmek gerekir.
Elimizde bir buffer olsun
char *buffer; 
Buffer'a ilk yapımızı yerleştirelim. Şöyle yaparız.
struct S
{ 
  ...
};

S *newS = new(buffer + offset)S; 
offset her zaman alignof(S)'nin katı olmalıdır.

3. Saçmalık
Sadece saçmalık olsun diye belleği malloc ile ayırıp yine placement new kullanabiliriz.
Örnek
Şöyle yaparız.
auto pBuffer = malloc(sizeof(ClassA)); 
ClassA *pObject = new (pBuffer) ClassA;
Örnek
Şöyle yaparız
int *p = (int*)malloc(sizeof(int));
new (p) int;
*p = 10;
4. Destructor
Bu şekilde nesne yaratıldıktan sonra delete T yapılamaz! Destructor'ın elle çağrılması gerekir.
Örnek
Şöyle yaparız.
p->~T();
Eğer destructor çağrılmaz ise ve bir kaynak yönetilmiyorsa sorun olmaz ancak kodun düzgün olması açısından çağırmak daha iyi.
Örnek
Şöyle yaparız.
void* operator new(std::size_t, void* p) { return p; }
struct X {
  X(int);
  ~X();
};
void f(X* p);

void g() {                      // rare, specialized use:
  char* buf = new char[sizeof(X)];
  X* p = new(buf) X(222);       // use buf[] and initialize
  f(p);
  p->X::~X();                   // cleanup
}
5. Placement new anti-pattern
Açıklaması şöyle.
In earlier versions of C++ there was no such thing as placement new; instead, developers used explicit assignment to this within constructors to achieve similar effect. This practice has been deprecated and abolished later, and third edition of The "C++ Programming Language" doesn't mention this technique. Support for placement new operator has been added to compilers circa 1995.
Bu kullanım şeklinde assignment operator için copy constructor'daki kodun aynısı yazmak istemeyen tembel programcı aşağıdaki gibi kodluyor. Yani copy constructor re-use edilmiş oluyor.

Ancak Herb Sutter bu kullanımın tehlikeli olabileceğine dikkat çekmiş. Tehlikeli olabilecek 5 tane madde sıralamış.
Applepie& Applepie::operator=( const Applepie &copy)
{

  if( this != &copy)
  {
     this->~Applepie(); // release own object
     new(this) Applepie(copy); // placement new copy constructor
  }
  return *this;
}

Hiç yorum yok:

Yorum Gönder