İngilizce Kaynaklar


Diğer




D'nin C++ ile Karşılaştırılması

Not: Bu bir D1 karşılaştırmasıdır. Modern D'nin başka farklılıkları da var.

Bu bölüm, C++ programcılarına yönelik olarak D dilinin nesne yönelimli olanaklarını C++'takilerle karşılaştırır. Daha alt düzey ve daha genel olanaklarını C karşılaştırmasında okuyabilirsiniz.

Bu sayfadaki bilgiler Digital Mars'ın sitesindeki aslından alınmıştır.



Kurucular

C++'ta kurucu fonksiyonlar sınıfla aynı isimdedirler; D'de ise this anahtar sözcüğü ile belirtilirler.

C++
class Foo
{
        Foo(int x); 
};
D
class Foo
{
        this(int x) { } 
}


Üst sınıf ilkleme
C++

Üst sınıflar ilk değer listesinde ilklenirler.

class A { A() {... } };
class B : A
{
     B(int x)
        : A()                // ust sinif kurucusu cagrilir
     {        ...
     }
};
D

Üst sınıfın kurucusu super() şeklinde çağrılır.

class A { this() { ... } }
class B : A
{
     this(int x)
     {        ...
        super();        // üst sınıf kurucusu çağrılır
        ...
     }
}

Böylece üst sınıf, alt sınıf kurucusunun herhangi bir noktasında kurulabilir. Ayrıca D'de bir kurucu fonksiyon içerisinden başka bir kurucu fonksiyon çağrılabilir:

class A
{       int a;
        int b;
        this() { a = 7; b = foo(); } 
        this(int x)
        {
            this();
            a = x;
        }
}

Hatta üyeler daha kurucu çağrılmadan bile sabit değerlerle ilklenebilirler. Aşağıdaki kod yukarıdakinin eşdeğeridir:

class A
{       int a = 7;
        int b;
        this() { b = foo(); } 
        this(int x)
        {
            this();
            a = x;
        }
}


Yapılarda eşitlik karşılaştırması
C++

C++'da yapı ataması çok basit olarak derleyici tarafından halledilir, ama eşitlik karşılaştırması için bir yardım gelmez.

#include <string.h>

struct A x, y;

inline bool operator==(const A& x, const A& y)
{
    return (memcmp(&x, &y, sizeof(struct A)) == 0); 
}
...
if (x == y)
    ...

operator== işlecinin her tür için ayrı ayrı tanımlanması gerekir. Ayrıca kod içinde == kullanımını görmek işleç fonksiyonu hakkında hiçbir fikir vermez, herhangi bir şekilde tanımlanmış olabilir.

memcmp ise her durumda çalışmayabilir, çünkü yapı üyeleri arasındaki olası doldurma bitlerinin [padding] hangi değerlerde olacakları standart tarafından tanımlanmamıştır.

O yüzden üyelerin teker teker karşılaştırılmaları gerekir ama bu da güvensizdir, çünkü sınıfa eklenen yeni bir üyenin bu fonksiyona da eklenmesinin unutulmaması gerekir.

D

D'de ise karşılaştırma da atama kadar basittir:

A x, y;
...
if (x == y) 
    ...


typedef'le yeni tür oluşturmak
C++

C++'da typedef yeni tür oluşturmaz; türe yeni bir isim verir.

#define CEREZ_ILK        ((Cerez)(-1))
typedef void *Cerez;
void foo(void *);
void bar(Cerez);

Cerez h = CEREZ_ILK;
foo(h);                        // farkedilmeyen kodlama hatasi
bar(h);                        // dogru kullanim

Böyle hatalardan kurtulmak için tür bir yapı içine alınır:

#define CEREZ_ILK        ((void *)(-1)) 
struct Cerez
{   void *isaretci;

    // ilk deger
    Cerez() { isaretci = CEREZ_ILK; }

    Cerez(int i) { isaretci = (void *)i; }

    // sarmalanan tUre dOnUsUm
    operator void*() { return isaretci; }
};
void bar(Cerez);

Cerez h;
bar(h);
h = fonksiyon();
if (h != CEREZ_ILK)
    ...
D

typedef yeni tür oluşturduğu için C++'daki gibi çözümler gerekmez.

typedef void* Cerez = cast(void*)-1; 
void bar(Cerez);

Cerez h;
bar(h);
h = fonksiyon();
if (h != Cerez.init)
    ...


friend sınıflar
C++

Bazı durumlarda başka sınıfın özel üyelerine erişmesi gereken sınıflar gerekebilir.

class A
{
    private:
        int a;

    public:
        int foo(B *j);
        friend class B;
        friend int abc(A *);
};

class B
{
    private:
        int b;

    public:
        int bar(A *j);
        friend class A;
};

int A::foo(B *j) { return j->b; }
int B::bar(A *j) { return j->a; } 

int abc(A *p) { return p->a; }
D

D'de ise aynı modülün içinde tanımlanmış olmak, birbirlerinin üyelerine erişebilmek için yeterlidir. Birbirleriyle yakından ilgili olan sınıfların aynı modülde bulunmaları nasıl doğalsa, birbirlerinin üyelerine erişmeleri de doğal kabul edilir.

module X;

class A
{
    private:
        static int a;

    public:
        int foo(B j) { return j.b; }
}

class B
{
    private:
        static int b;

    public:
        int bar(A j) { return j.a; } 
}

int abc(A p) { return p.a; }


İşleç yükleme [operator overloading]
C++

Aritmetik işlemlerde kullanılabilen bir sınıf için karşılaştırma işleçleri tanımlamak isteyelim. Toplam sekiz fonksiyon gerekir:

struct A
{
        int operator <  (int i);
        int operator <= (int i);
        int operator >  (int i);
        int operator >= (int i);
};

int operator <  (int i, A &a) { return a >  i; }
int operator <= (int i, A &a) { return a >= i; }
int operator >  (int i, A &a) { return a <  i; }
int operator >= (int i, A &a) { return a <= i; } 
D

D, karşılaştırma işleçlerinin önemini ve birbirleriyle olan doğal ilişkilerini kabul eder, ve tek bir fonksiyon gerektirir:

struct A
{
        int opCmp(int i); 
}

Derleyici bütün <, <=, >, ve >= işleçlerinin ve sol tarafın nesne olmadığı serbest fonksiyonların hepsini; programcının tanımladığı bu tek karşılaştırma fonksiyonundan çıkarsar.

Derleyici diğer işleç yüklemelerinde de benzer yardımlar sunar ve programcının aynı sonucu elde etmesi için çok daha az kod yazması gerekir.



İsim alanı kısaltan using bildirimleri

D'de isim alanlarının ve başlık dosyalarının yerine modüller kullanılır. using bildirimleri yerine alias'lar vardır.

C++
namespace foo
{
    int x;
}
using foo::x;
D
/** foo.d modülü **/
module foo;
int x;

/** Başka bir modül **/ 
import foo;
alias foo.x x;

Aslında alias'ın başka kullanımları da vardır: isimlere yeni adlar verir, şablon üyelerinden ve iç içe sınıflardan bahsederken kolaylık sağlar.



RAII (Resource Acquisition Is Initialization)
C++

Bozucu fonksiyonlar, kapsamlardan hata atıldığı durumlarda çıkılırken bile çağrıldıkları için; C++'da kaynaklar bozucu fonksiyonlarda geri verilmek zorundadırlar.

class Dosya
{   Cerez *h;

    ~Dosya()
    {
        h->geri_ver(); 
    }
};
D

Kaynak sorunlarının başında gelen bellek yönetimi D'de normalde çöp toplayıcı tarafından halledilir. Diğer bir tür kaynak, iş parçacıklarında [thread] yararlanılan bayraklar ve kilitlerdir; bunlar da normalde D dilinin iş parçacıklarına verdiği destekle halledilir.

Bunların dışında gereken RAII durumlarında da kapsam [scope] sınıflarından yararlanılır. Kapsam sınıflarının bozucu fonksiyonları C++'da olduğu gibi, kapsamlardan çıkılırken çağrılırlar.

scope class Dosya
{   Çerez h;

    ~this()
    {
        h.geri_ver();
    }
}

void deneme()
{
    if (...)
    {   scope f = new Dosya();
        ...
    } // Kapsamdan bir hata atılarak bile çıkılmış olsa
      // f.~this() bu kapama parantezinde çağrılır
}


Nitelikler [properties]
C++

Sınıfların nitelik olarak adlandırabileceğimiz üyeleri geleneksel olarak değerlerini okumak için get, değerlerini değiştirmek için de set fonksiyonları yoluyla kullanılırlar.

class Abc
{
  public:
    void set_bir_nitelik(int yeni_deger)
    {
        bir_nitelik = yeni_deger;
    }

    int get_bir_nitelik() { return bir_nitelik; }

  private:
    int bir_nitelik;
};

Abc a;
a.set_bir_nitelik(3);
int x = a.get_bir_nitelik();
D

Nitelikler üye nesne yazımıyla kullanılırlar ama atama veya okuma arka planda fonksiyonlar tarafından hallediliyor olabilir:

class Abc
{
    // değeri değiştir
    void bir_nitelik(int yeni_değer)
    {
        benim_değerim = yeni_değer;
    }

    // değeri döndür
    int bir_nitelik() { return benim_değerim; }

  private:
    int benim_değerim;
}

Sanki bir üye nesneymiş gibi kullanılır:

Abc a;
a.bir_nitelik = 3;     // a.bir_nitelik(3) ile aynı şey
int x = a.bir_nitelik; // int x = a.bir_nitelik() ile aynı şey

Bu sayede, başlangıçta gerçekten üye nesne olarak tasarlanmış olan bir üyenin daha sonradan fonksiyonlar tarafından halledilmesi gerekse; kullanıcı kodunda hiçbir değişiklik yapmadan, salt sınıf tanımını değiştirmek yeterli olur.



Özyinelemeli şablonlar
C++

Şablonların ileri düzey kullanımlarından birisi, onları özyinelemeli olarak kullanmak ve özyinelemeyi özel bir şablon tanımıyla [specialization] sonlandırmaktır. Örneğin faktöriyel hesaplayan bir şablon şöyle yazılabilir:

template<int n> class faktoriyel
{
    public:
        enum { sonuc = n * faktoriyel<n - 1>::sonuc }; 
};

template<> class faktoriyel<1>
{
    public:
        enum { sonuc = 1 };
};

void deneme()
{
    printf("%d\n", faktoriyel<4>::sonuc); // 24 yazar
}
D

D'de de temelde aynıdır ama tek olan şablon üyelerinin üst kapsamda görünebilmeleri olanağı yoluyla daha basittir:

template faktöriyel(int n)
{
    enum { faktöriyel = n * .faktöriyel!(n-1) }
}

template faktöriyel(int n : 1)
{
    enum { faktöriyel = 1 }
}

void deneme()
{
    writefln("%d", faktöriyel!(4));        // 24 yazar
}


Meta şablonlar

Problem: En az bit_adedi kadar bitten oluşan işaretli bir türe typedef yoluyla yeni bir isim verin.

C++

Bu kod Dr. Carlo Pescio'nun Template Metaprogramming: Make parameterized integers portable with this novel technique adlı yazısından basitleştirilerek uyarlanmıştır.

Derlemeyi şablon parametrelerine bağlı bir ifadenin sonucuna göre yönlendirmek C++'da mümkün değildir. Bu yüzden, şablonu ifadenin çeşitli değerleri için özel olarak tanımlamak gerekir. Daha kötüsü, özel tanımları "küçüktür veya eşittir" gibi koşullara göre yapmak da mümkün olmadığı için, ifade değerini bir arttırmak gibi özyinelemeli yöntemlerden yararlanmak gerekir. Derleyicinin uygun bir özel tanım bulamaması durumunda da ya derleyici göçer, ya da anlaşılması hemen hemen imkansız bir hata mesajı verilir.

C++'da şablon typedef'leri olmadığı için de bazen makrolardan da yararlanmak gerekebilir.

#include <limits.h>

template< int bit_adedi > struct Sayi
{
    typedef Sayi< bit_adedi + 1 > :: int_tUrU int_tUrU ;
} ;

struct Sayi< 8 >
{
    typedef signed char int_tUrU ;
} ;

struct Sayi< 16 > 
{
    typedef short int_tUrU ;
} ;

struct Sayi< 32 > 
{
    typedef int int_tUrU ;
} ;

struct Sayi< 64 >
{
    typedef long long int_tUrU ;
} ;

// Temel turlerin istenen bit adedini tam karsilamadigi
// durumlarda, bu metaprogram bit_adedi'ni bir derleyici
// hatasi olusana veya INT_MAX'e varilana kadar oteler.
// Sablonun INT_MAX ozel tanimi int_tUrU'nu tanimlamadigi
// icin de sonunda mutlaka bir derleme hata mesaji alinir.
struct Sayi< INT_MAX >
{
} ;

// Yazim kolayligi acisindan
#define Sayi( bit_adedi ) Sayi< bit_adedi > :: int_tUrU 

#include <stdio.h>

int main()
{
    Sayi( 8 ) i ;
    Sayi( 16 ) j ;
    Sayi( 29 ) k ;
    Sayi( 64 ) l ;
    printf("%d %d %d %d\n",
        sizeof(i), sizeof(j), sizeof(k), sizeof(l)); 
    return 0 ;
}
Boost'un C++ çözümü

Bu da David Abrahams'ın Boost kütüphanesini kullanan çözümü:

#include <boost/mpl/if.hpp>
#include <boost/mpl/assert.hpp>

template <int bit_adedi> struct Sayi
    : mpl::if_c<(bit_adedi <= 8), signed char
    , mpl::if_c<(bit_adedi <= 16), short
    , mpl::if_c<(bit_adedi <= 32), long
    , long long>::type >::type >
{
    BOOST_MPL_ASSERT_RELATION(bit_adedi, <=, 64);
}

#include <stdio.h>

int main()
{
    Sayi< 8 > i ;
    Sayi< 16 > j ;
    Sayi< 29 > k ;
    Sayi< 64 > l ;
    printf("%d %d %d %d\n",
        sizeof(i), sizeof(j), sizeof(k), sizeof(l)); 
    return 0 ;
}
D

Özyinelemeli şablonlar kullanılabilse de, D'de çok daha iyi bir yol vardır ve C++'daki çözümlere kıyasla burada neler olduğunu anlamak da çok kolaydır. Derlenmesi çok hızlıdır, ve hata mesajları da çok daha anlaşılırdır:

import std.stdio;

template Sayi(int bit_adedi)
{
    static if (bit_adedi <= 8)
        alias byte Sayi;
    else static if (bit_adedi <= 16)
        alias short Sayi;
    else static if (bit_adedi <= 32)
        alias int Sayi;
    else static if (bit_adedi <= 64)
        alias long Sayi;
    else
        static assert(0);
}

int main()
{
    Sayi!(8) i ;
    Sayi!(16) j ;
    Sayi!(29) k ;
    Sayi!(64) l ;
    writefln("%d %d %d %d",
        i.sizeof, j.sizeof, k.sizeof, l.sizeof); 
    return 0;
}


Tür özellikleri [type traits]

Tür özellikleri derleme zamanında kullanışlıdırlar.

C++

Parametresinin bir fonksiyon olup olmadığını anlayan bu şablon, David Vandevoorde ve Nicolai M. Josuttis'in C++ Templates: The Complete Guide kitaplarının 353'üncü sayfasından uyarlanmıştır:

template<typename T> class Fonksiyondur
{
    private:
        typedef char Bir;
        typedef struct { char a[2]; } Iki;
        template<typename U> static Bir oyle_mi(...);
        template<typename U> static Iki oyle_mi(U (*)[1]);
    public:
        enum {
          Evet = sizeof(Fonksiyondur<T>::oyle_mi<T>(0)) == 1
        };
};

void deneme()
{
    typedef int (fp)(int);

    assert(Fonksiyondur<fp>::Evet == 1);
}

Bu çözüm SFINAE (Substitution Failure Is Not An Error) kuralından yararlanır ve oldukça ileri düzey şablon olanakları kullanır. [Çevirenin notu: C++ dilinin bir özelliği olan SFINAE, şablon parametresi çıkarsarken yasal olmayan türlerle karşılaşıldığında derleyicinin hata vermek yerine, o özel tanımı gözardı edeceğini söyleyen kuraldır.]

D

SFINAE D'de çok basit bir yolla yapılabilir:

template Fonksiyondur(T)
{
    static if ( is(T[]) )
        const int Fonksiyondur = 0;
    else
        const int Fonksiyondur = 1;
}

void deneme()
{
    typedef int fp(int);

    assert(Fonksiyondur!(fp) == 1);
}

Daha da iyisi, bir türün bir fonksiyon olup olmadığını anlamak için şablon yöntemlerine başvurmaya gerek de yoktur. D'nin is ifadesi bu soruyu doğrudan yanıtlar:

void deneme()
{
    alias int fp(int);

    assert( is(fp == function) );
}