Thread overview
Emekli Olmuş Posblit
June 23

Merhaba,

Geçen sene şu tartışma içerisinde duyurusu yapılmış:

https://forum.dlang.org/post/2023.14438@ddili.org

Ama ben copy constructor'ü denemeden önce bunu anlamak için diye şöyle bir kod yazdım:

/* __postblit; Denemesi
 * Tarih: 22.06.2021
 */
struct A
{
    int i;

    this(this)
    {
      import core.stdc.stdio : printf;
      printf("postblit öncesi i = %d\n", i++);
    }
}

import std.stdio;

void main()
{
    auto a = A.init;
    writeln("struct A kuruldu...");

    a.__postblit;
    writeln("this()'de yazıldı --^\n");
    a.i.writeln("<-- main()'de yazıldı!");

    /* bu satır açıldığında this()'e
    writeln(a); // 4'e kere giriyor,
    writefln("A(4) == %s ", a); // bu 3!
    a.i.writeln(": i'nin değeri aynı!");//*/
}

Her şey olması gerektiği gibi ilerlerken, kolaylık olsun diye yapıyı ekrana yazdırırsam ne olur diye denediğimde ekrana defalarca satır yazması tuhafıma gitti:

struct A kuruldu...
postblit öncesi i = 0
this()'de yazıldı --^

1<-- main()'de yazıldı!
postblit öncesi i = 1
postblit öncesi i = 2
postblit öncesi i = 3
postblit öncesi i = 4
A(5)
postblit öncesi i = 1
postblit öncesi i = 2
postblit öncesi i = 3
A(4) == A(4)
1: i'nin değeri aynı!

Aynı çıktıyı alabilmek için gizlediğim satırları bir / karakteri ekleyerek açmalısınız. Neden böyle oluyor diye sorgulamıyorum ki zaten kullanılması önerilmiyormuş.

Ama niye writeln() 4, writef() 3 kere posblit kısmına girer gibi yapar anlayamadım. Tuhaf!

Başarılar...

June 23

Sanırım böyle daha iyi ve beklendiği gibi tüm satırlar düzgün çalışıyor:

import std.stdio;

struct A (T)
{
  T i;

  @disable this();
  this(T n)
  {
    i = n;

    writef("init sonrası i = %.3f\n", i);
    i+=0.877;
  }
}

void main()
{
  auto a = A!double(0.123);

  writeln("this()'de yazıldı --^\n");
  writef("i = %.3f, %s\n", a.i, a);
} /* ÇIKTISI:
init sonrası i = 0.123
this()'de yazıldı --^

i = 1.000, A!double(1)
*************************/
June 25
On 6/22/21 11:19 PM, Salih Dincer wrote:

>      this(this)
>      {

Post-blit, nesne kopyalandıktan sonra otomatik olarak ve *bazen* işletilir. Amacı, kopyalanan nesnenin asıl nesneyle aynı kaynakları paylaşmasını önlemektir. Bir başka deyişle, paylaşılmaması gereken kaynakların kopya için ek olarak ayrılması gibi amaçlarla kullanılır.

Ama, eğer nesne teknik olarak kopyalanıyorsa ama asıl nesne artık yaşamayacaksa post-blit işletilmeyebilir çünkü aynı kaynakları paylaşmakta olan iki nesne olmayacaktır. D'nin bu özelliği, C++'ın "move constructor" vs. olanaklarının D'de gerekmemesinin nedenidir. Derleyici çoğu durumda doğrusunu yapar.

>      a.__postblit;

Tabii hiç öyle açıkça çağrılması düşünülmemiştir ve normal programlarda öyle çağrılmaz; kopya sonrasında otomatik olarak işletilir.

>      /* bu satır açıldığında this()'e
>      writeln(a); // 4'e kere giriyor,

writeln, 'a' nesnesini kopya olarak alıyor. Post-blit o yüzden işletiliyor.

>      writefln("A(4) == %s ", a); // bu 3!

O da aynı nedenden...

Ali


June 25
On 6/23/21 6:35 AM, Salih Dincer wrote:

> Sanırım böyle daha iyi ve beklendiği gibi tüm satırlar düzgün çalışıyor:

Ama bu örnekte zaten post-blit yok. :) Nesne yine de bit bit kopyalanıyor ama tanımlanmadığından post-blit işletilmiyor.

Post-blit'in işleyişiyle ilgili ben de şöyle bir program düşünebilirim:

import std.stdio;

struct S {
  S*[] soyağacı;

  @disable this();

  this(int /* parametreyi kullanmıyoruz */) {
    hatırla();
  }

  this(this) {
    hatırla();
  }

  void hatırla() {
    soyağacı ~= &this;
  }

  void göster(string açıklama) const {
    writefln!"%s: %s"(açıklama, soyağacı);
  }
}

void main() {
  auto a = S(42);
  a.göster("main.a");

  // Burada post-blit işletiliyor çünkü b, a'nın kopyası.
  auto b = a;
  b.göster("main.b");

  // Bu işlev çağrılırken kopyalanıyor çünkü foo, 'b'nin
  // kopyasını alıyor.
  foo(b);

  // Burada eniyileştirmeler var çünkü teknik olarak
  // "kopyalar" olduğu halde hiç post-blit
  // işletilmiyor. Teknik olarak "kopya"lar şunlar:
  //
  // 1) döndür'ün döndürdüğü nesne çağırana kopyalanır
  // 2) hesapla'nın aldığı parametre kopyalanır
  // 3) hesapla'nın döndürdüğü nesne çağırana kopyalanır
  auto hesaplanan = hesapla(döndür());
  hesaplanan.göster("main.hesaplanan");
}

void foo(S s) {
  s.göster("foo.s");
}

S hesapla(S giriş) {
  giriş.göster("hesapla.giriş");
  auto sonuç = S(43);
  return sonuç;
}

S döndür() {
  auto sonuç = S(44);
  return sonuç;
}

Çıktısında görüldüğü gibi, main'in sonundaki 'hesaplanan' nesnesi için post-blit işletilmiyor:

main.a: [7FFC3A270360]
main.b: [7FFC3A270360, 7FFC3A270370]
foo.s: [7FFC3A270360, 7FFC3A270370, 7FFC3A270380]
hesapla.giriş: [7FFC3A2703B0]
main.hesaplanan: [7FFC3A2703A0]

Not: Programı işletirken 2.097 şu uyarıyı veriyor: "Deprecation: copying `&this` into allocated memory escapes a reference to parameter variable `this`". Olsun; görmek istediğime engel olmadı...

Ali


June 26

Katkı için müteşekkirim...

Çünkü harika bir örnekle iç içeyiz (<- bu sözcüğü özellikle seçtim çünkü örnekler bize bunu çağrıştırıyor!)

On Friday, 25 June 2021 at 17:18:44 UTC, Ali Çehreli wrote:

>

// Burada post-blit işletiliyor çünkü b, a'nın kopyası.
auto b = a;
b.göster("main.b");

Bu beklediğim bir şey çünkü nesnenin aslı korunuyor. Böylece a ve b birbirinden bağımsız işletilebilir. Ayrıca kopyalanırken parametre almayan this(this) vasıtasıyla hatırla() işlevi, ikinci S() yapısının adresini soyağacı'na not alıyor.

On Friday, 25 June 2021 at 17:18:44 UTC, Ali Çehreli wrote:

>

// Bu işlev çağrılırken kopyalanıyor çünkü foo, 'b'nin
// kopyasını alıyor.
foo(b);

Bunu da bekliyordum çünkü foo() işlevi ref ile parametre almıyor; alsaydı hala 2 kopyamız olurdu.

Peki 3. kopya nerede? Sanırım GC'ye emanet ama hemen sonraki şu satırlarda yaşadığını kanıtlayabiliriz:

  auto ikincininAdresi = b.soyağacı[1];
  auto foo_nunKendisi = *(ikincininAdresi++);
  foo_nunKendisi.soyağacı.length.writeln;

Çünkü ikinci kopyanın içindeki dizinin 2 elemanı var. Biz bilmediğimiz üçüncü kopyayı, bildiğimizi bir arttırarak bulabiliriz. En azından yüksek olasılıkla orada olduğunu tahmin ediyoruz ve voilà dizinin uzunluğu 3...

On Friday, 25 June 2021 at 17:18:44 UTC, Ali Çehreli wrote:

>

// Burada eniyileştirmeler var çünkü teknik olarak
// "kopyalar" olduğu halde hiç post-blit
// işletilmiyor.

Bu doğal çünkü @disable this(); ile post-blit devre dışı bırakılmıyor mu? Bunu konuyu açtıktan sonra yazdığım 2. örnekte gösterdim.

Kodun 2. bölümünü biraz incelemem gerekiyor. Konuyu derleyici ile birlikte etüt edip tekrar yazacağım... :)

June 26
// D 2.0.83
import std.stdio;

struct S {
  int[] kimliği;
  /*** DİKKAT: Sonuç değişir mi diye @disable bölümünü
               kaldırdım, değişmedi! ***/
  this(int id) {
    hatırla(id);
    "(.)".write;
  }

  this(this) {
    hatırla();
    "(..)".write;
  }

  void hatırla(int id = 0) {
    kimliği ~= id;
  }

  void göster(string açıklama) const {
    writefln!"%s: %s"(açıklama, kimliği);
  }
}

void main() {
  auto a = S(42);
  a.göster("ana.a");

  auto b = a;
  b.göster("ana.b");

  foo(b);
  S* p = &b;

  bar(*++p);
  writeln(); // 2. bölüm örnekleri:

  auto hesaplanan = hesapla(döndür());
  hesaplanan.göster("ana.hesaplanan");
} /* ÇIKTISI:
  (.)ana.a: [42]
  (..)ana.b: [42, 0]
  (..)foo.s: [42, 0, 0]
  (..)bar.s: [42, 0, 0, 0]

  (.)hesapla.giriş: [44]
  (.)ana.hesaplanan: [43]

NOT: Çift noktalar this(this)'e girdiğine işaret
eder. Açıkca id parametresi almayan kopyalanmıyor.
*/

Hocam izninizle ana kodu biraz değiştirdim. Bu sefer işaretçiler yerine kimliği önplana çıkardım. Ayrıca kaybolma ihtimali olan ve foo()'da oluşan yapıyı bar()'a refere ederek, zinciri biraz daha ilerletip ilerletemediğimi denedim. Sonuç tatmin edici.

2.bölüm örneklerine gelince. Neticede yeni bir scope içinde ve yeni S(44 ve 43) yapısı oluşmakta. Yani var olanın (aynı scope içindekini) kopyalamıyor ve auto ile yeniden oluşturulan yapı return ile (tabii ki referansı aslında değil mi?) döndürülüyor.

Peki bu işlevlerdeki (döndür ve hesapla) S() yapısının yaşam süresi main() kadar mıdır?

June 27
On 6/25/21 10:30 PM, Salih Dincer wrote:

> Peki 3. kopya nerede? Sanırım GC'ye emanet

Burada GC ile hiç işimiz yok. Bütün nesneler işlev çağı yığıtında (function call stack).

Teorik olarak 3 kopya var ama hiç kopyalanmıyor. Benim gördüğüm 3 teorik kopya şunlar:

  // 1) döndür'ün döndürdüğü nesne çağırana kopyalanır
  // 2) hesapla'nın aldığı parametre kopyalanır
  // 3) hesapla'nın döndürdüğü nesne çağırana kopyalanır

dmd'nin de yaptığı gibi, böyle ifadelerde eniyileştirmeler nedeniyle hiç kopya olmaz.

> On Friday, 25 June 2021 at 17:18:44 UTC, Ali Çehreli wrote:
>>   // Burada eniyileştirmeler var çünkü teknik olarak
>>   // "kopyalar" olduğu halde hiç post-blit
>>   // işletilmiyor.
>
> Bu doğal çünkü ``` @disable this(); ```  ile post-blit devre dışı
> bırakılmıyor mu?

Post-blit'in devre bırakmak için şöyle yazardık:

  @disable this(this);

Ben varsayılan kurucuyu kendimden emin olmak çin devre dışı bıraktım. Böylece, yanlışlıkla S() yazmam sonucunda ortalıkta soyağacı boş olan nesne bulunmayacağını garantilemiş oldum.

Ali


June 27
On 6/26/21 5:55 AM, Salih Dincer wrote:

> Peki bu işlevlerdeki (döndür ve hesapla) S() yapısının yaşam süresi
> main() kadar mıdır?

Öyle geçici nesnelerin yaşam süreçleri, oluşturuldukları ifade sonuna kadardır. Yapıya ~this() işlevi ekleyerek deneneyebiliriz:

~this() {
  writefln!"%s sonlandırılıyor"(kimlik);
}

Ali