Can ve Erdem zaten güzelce yanıtlamışlar. Ben tekrarlıyorum.
Öncelikle, dil kavramları ile gerçekleştirme kavramlarının birbirlerinden ayrı tutulmaları gerektiğini söylemek istiyorum. Bartosz Milewski gibi bir ustanın benimle aynı fikirde olduğunu biliyorum. Walter ve Andrei'nin bile dil olanaklarını tanımlarken 'stack'ten bahsetmelerinin yanlış olduğunu düşünüyorum.
İlgili olarak, bazen de forumlara katılan D'ciler yeni başlayanlara D'nin dilimlerini anlatırken "nasıl tanımlandığını bir görsen hemen anlarsın" diye açıklıyorlar. "Bak şu adreste bir resim var, içindeki gösterge elemanları nasıl da gösteriyor" diyorlar. O da yanlış. Dil, bu gibi alt düzey kavramlardan soyutlanmıştır. Hiçbir yeni başlayan, dilimleri anlamak için derleyicinin perde arkasında ne işlerle uğraştığını bilmek zorunda değildir ve olmamalıdır. (Ayrıca, eğer dilim kavramını gerçekleştirmesine bakmadan anlayamıyorsak o dilde bir hata var demektir. D'nin dilimleri bir noktada öyleydi.)
Dilim bir kavramdır. Dilim işlemlerinin sonuçlarında ne olacağı bellidir. Örneğin 'dilim[j]' işlemi anlatılırken hiç kimse "derleyici perde arkasındaki eleman göstergesine j'yi ekler ve sonuçta elde ettiği adresteki nesneye * işleci ile eriştirir" demez. Doğrusu, "j'inci elemana erişim sağlar"dır. Bunun nasıl gerçekleştirildiği dilden bağımsız bir olaydır.
Program yığıtı (program stack) konusu da böyledir.
C++ standardı bu konuda çok başarılıdır: yerel değişkenlerin yığıtta bulunmaları gerektiğinden filan bahsetmez. C++ standardının gözünde örneğin "yaşamı otomatik olarak idare edilen" değişkenler vardır. O çeşit değişkenlerin yerlerinin mikro işlemcinin 'stack pointer' düzeneği ile yıldırım hızında ayrılıyor olmaları ve yine aynı hızda geri veriliyor olmaları dili ilgilendirmez. Evet, o nesneler orada yaşarlar ama C++ tanımı açısından bunun sözü edilmez. (Walter ve Andrei'nin de C++ standardı kadar özenli olmalarını umardım.)
Karşıt örnekler: ben iki adet 'stack pointer'ı olan bir mikro işlemci tasarlasam Walter'a gidip otomatik nesneler için hangisini kullanacağımı mı sormalıyım? Tabii ki olamaz. Veya 'stack pointer'ı olmayan mikro işlemci tasarlasam onun için bir D derleyicisi yazamayacak mıyım? Tabii ki olmaz.
Tabii gerçekte bütün (modern?) mikro işlemcilerde 'stack pointer' diye bir yazmaç (register) vardır ve bütün otomatik değişkenler onun gösterdiği yerde yaşarlar. İşte bu gerçek yüzünden "bu nesnenin yaşamı otomatik olarak sonlandırılır" yerine "bu nesne stack'te yaşar" deniyor. Ama denmemeli. Çünkü yeni başlayan birisine dilin gözünde nesne yaşamlarını anlatmak kolaydır ve bunları bilmek doğru programlar üretmek için şarttır; ama 'stack'ten bahsetmeye başlayınca ya yeni başlayanların mikro işlemci ve derleyici teknolojilerini de bilmelerini beklemiş oluyoruz, ya da kafalarına yarım doğrular sokuyoruz.
Alıntı:
> Yapılar, sınıflar ile büyük benzerleklik gösterirler. Sınıf gibi tanımlanırlar. Hatta sınıflar gibi, özellikler,metodlar,veriler, yapıcılar vb... içerebilirler. Buna karşın sınıflar ile yapılar arasında çok önemli farklılıklar vardır.
Herşeyden önce en önemli fark, yapıların değer türü olması ve sınıfların referans türü olmasıdır.
Aynen D'de de öyle.
Alıntı:
> Sınıflar referans türünden oldukları için, bellekte tutuluş biçimleri değer türlerine göre daha farklıdır.
O kadarı da doğru çünkü sınıflarda değişken ile nesne diye iki kavram var. Nesne her zaman için isimsizdir; programda isimli olarak gördüğümüz, değişkendir.
Alıntı:
> Referans tiplerinin sahip olduğu veriler belleğin öbek(heap) adı verilen tarafında tutulurken, referansın adı stack(yığın) da tutulur ve öbekteki verilerin bulunduğu adresi işaret eder. Ancak değer türleri belleğin stack denilen kısmında tutulurlar.
C# bilmiyorum ama o söylenenler D için ancak bazı durumlarda geçerlidir. Çürütelim... Şu örnekteki üye nerededir?
struct Yapı
{}
class Sınıf
{
Yapı üye; // <-- nerede?
BaşkaSınıf üye2;
}
Yapı olduğu için 'stack'te midir? Yoksa bir sınıfın üyesi olduğu için öbekte midir? Peki şuradaki int ve hatta onu sarmalayan yapı nerededir?
struct Yapı
{
int i;
}
İstersek 'stack'tedir, istersek öbektedir:
import std.stdio;
struct Yapı
{
int i;
}
void main()
{
auto nesne0 = Yapı(); // 'stack'te
auto nesne1 = new Yapı(); // öbekte
writeln(&nesne0.i);
writeln(&nesne1.i);
}
Çıktısından anlaşıldığı gibi benim ortamımda iki nesne bambaşka bölgelerde yaşıyorlar:
'FF88C8E8
F7428E20
'
(FF ve F7 ile başlayan bölgeler birbilerinden çok uzaktalar.)
Alıntı:
> Ne zaman yapı ne zaman sınıf kullanmalıyız?
C# için ne yanıt verileceğini bilemem.
D'de en genel olarak, tür bir değer ifade ediyorsa yapı kullanılır. Örneğin nesnelerin kimlikleri önemli değilse... Bir Piyon nesnesi diğerinden farklı mıdır? Programına göre değişir. Eğer Piyon programda değer olarak kullanılıyorsa struct olmalıdır.
Daha özel durumlarda çok şekilliği düşünmek gerekir. SatrançTaşı diye bir arayüz varsa ve her taş kendi bildiği davranışlara sahipse o zaman Piyon bir sınıf olmalıdır. Örneğin bir satranç taşına aslında ne olduğunu bilmeden "şu kareye gitmen yasal mıdır" diyebiliyorsak o zaman bütün satranç taşları sınıf türleridir.
Öncelikle böyle anlamsal açılardan yaklaşmak gerekir. Nesnelerin nerede durdukları ve aşağıdaki gibi performans sorunları ikincil önemdedir.
Alıntı:
> Özellikle metodlara veriler aktarırken bu verileri sınıf içerisinde tanımladığımızda, tüm veriler metoda aktarılacağını sadece bu verilerin öbekteki başlangıç adresi aktarılır ve ilgili parametrenin de bu adresteki verilere işaret etmesi sağlanmış olur.
D dilinde: "işlevlere sınıf değişkenlerinin kopyaları aktarılır; isimsiz sınıf nesnesi o iki kopya tarafından eriştirilmeye devam eder." Gösterelim:
import std.stdio;
class Sınıf
{
int i;
}
void foo(Sınıf değişken)
{
writeln("foo içinde değişken : ", &değişken);
writeln("foo içinde değişken.i : ", &(değişken.i));
}
void main()
{
auto değişken = new Sınıf;
writeln("main içinde değişken : ", &değişken);
writeln("main içinde değişken.i: ", &(değişken.i));
foo(değişken);
}
Çıktısından görüldüğü gibi iki farklı değişkenden söz ediyoruz ama tek bir nesne var. i üyesinin adresi aynı:
'main içinde değişken : FFC55B4C
main içinde değişken.i: F736BE28
foo içinde değişken : FFC55B38
foo içinde değişken.i : F736BE28
'
Alıntı:
> Böylece büyük boyutlu verileri stack'ta kopyalayarak gereksiz miktarda bellek harcanmasının önüne geçilmiş olunur.
Bellek harcanmamasını anlarım ama onun referans türlerinin nedeni olarak gösterilmesi yanlış. Sınıf nesnelerinin kopyalanmamalarının nedeni, kimlikleri olan nesneler olmalarıdır. Sınıf nesneleri yeni kimliğe sahip olmasınlar diye kopyalanmazlar.
Tekrar altını çiziyorum: İşleve sınıf türü gönderilirken
-
değişken kopyalanır
-
nesne kopyalanmaz
Kaldı ki, D'de gösterge diye bir kavram var. Eğer istersek korkunç derecede büyük yapıları bile gösterge ile geçirebiliriz:
struct Yapı
{
// ... korkunç büyük olsun ...
}
void foo(Yapı * nesne)
{
// ...
}
void main()
{
auto nesne = Yapı();
foo(&nesne); // <-- değer türü olduğu halde kopyalamadık
}
Gördüğünüz gibi, değer ve referans türü seçiminde veri büyüklüğünün hiçbir önemi yoktur.
Alıntı:
> Ancak küçük boyutlarda veriler ile çalışırken bu verileri sınıflar içerisinde kullandığımızda bu kezde gereksiz yere bellek kullanıldığı öbek şişer ve performans düşer. Bu konudaki uzman görüş 16 byte'tan küçük veriler için yapıların kullanılması, 16 byte'tan büyük veriler için ise sınıfların kullanılmasıdır.
Tekrar söylüyorum; C# bilmiyorum ama eğer aynı şeyi C, C++, veya D için de söyleyeceklerse o uzmanlarla tanışmak isterdim. :) Eğer programlarınızı bu tür kaygılara göre yazacağımızı düşünmeye başladıysanız hemen durun! Türlerin yapı veya sınıf olacaklarına büyüklüklerine bakarak karar vermek programcılık cinayetidir.
Ayrıca mantık dışı ve yarım yamalak bir öğüttür. Düşünelim: kütüphanemizdeki 16 baytlık bir türü bugün yapı olarak tasarlamışken iki ay sonra 20 bayta çıktı diye sınıf mı yapacağız? Kullanıcı kodları ne olacak?
Ayrıca bunun tarih öncesinden beri geçerli olan bir adı bile var: "premature optimization is the root of all evil" ("Gereğinden önce yapılan eniyileştirme her kötülüğün anasıdır").
Alıntı:
> Bu durum D içinde geçerli midir acaba ve eğer geçerliyse bizler kodlarımızı tasarlarken nelere dikkat etmeliyiz?
D için geçerli değildir ve eğer C#'ta gösterge kavramı varsa orada da geçerli olduğunu sanmıyorum.
Ali
--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]