C++11- nullptr != NULL

C++11 standartlarıyla beraber C++ programlama dili yavaş yavaş daha deterministik olmaya başladı. Yani belirsiz durumlar, kullanım hataları dilin standartlarının desteğiyle en aza çekiliyor. Bu kapsamda nullptr de uzun zamandır bilinen bir sorunu çözüyor.

#include <iostream>
#include <memory>


using namespace std;



void func(int x) 
{ 
    cout << "int" << endl; 
}

void func(char *y) 
{
    cout << "pointer" << endl; 
}
 
int main() {
    func(0);        // int overload
    func((char *)0); // char * overload
    func(nullptr);  // char* overload
    func(NULL);     // main.cpp:23:14: error: call of overloaded ‘func(NULL)’ is ambiguous (compiler: g++5)
}

Üstteki hata aslında derleyiciden derleyiciye farklı olabilir, örneğin visual studio da ‘int’ olarak yorumlanabilir. Kısacası non-deterministic bir durum sözkonusu, derleyicinden derleyiciye farkediyor.
Aslında sorun C++ programlama dilinin iki temel özelliğinden kaynaklanıyor; Function Overloading ve Otomatik Tür dönüşümü. Şimdi bir de NULL isminin nasıl tanımlandığına bakalım:

#if defined (_STDDEF_H) || defined (__need_NULL)
#undef NULL		/* in case <stdio.h> has defined it. */
#ifdef __GNUG__
#define NULL __null
#else   /* G++ */
#ifndef __cplusplus
#define NULL ((void *)0)
#else   /* C++ */
#define NULL 0
#endif  /* C++ */
#endif  /* G++ */
#endif	/* NULL not defined and <stddef.h> or need NULL.  */
#undef	__need_NULL

Görüldüğü üzere derleyiciniz cplusplus derleyicisi ise NULL ismi 0 olarak tanımlanmış. İşte tam da burda sıkıntı ortaya çıkıyor.

Derleyici ‘func(NULL)’ dediğinizde hangi fonksiyonu çağıracağını bilmiyor(g++5 için) ya da kullandığınız başka bir derleyici de farklı bir davranış gözlemleyebilirsiniz integer veya char * function overloadlarından biri çağırılabilir. Hülasası başta da belirttiğim gibi C++11 bu tarz non-deterministic konulara da el atıyor ve bu meseleyi nullptr ile çözüyor. Artık NULL yazdığınız her yere nullptr yazarak daha güvenli programlama yapmış olabilirsiniz özellikle cross-platform yazılım geliştiyorsanız.

nullptr’ nin aslında türü std::nullptr_t ve bir prvalue’ dur. Dolayısıyla herhangi bir şekilde deference edilemez.

Bunun yanı sıra nullptr meta programmingte perfect forwardinge de olanak sağlamaktadır. Şöyle ki:

#include <iostream>
#include <memory>


using namespace std;


template<class F, class A>
void foo(F f, A a)
{
    f(a);
}
 

void func(char *y) 
{
    cout << "pointer" << endl; 
}
 
int main() {
    func(0);        // int overload
    func((char *)0); // void * overload
    func(nullptr);  // void * overload
    
    //foo(func, NULL); //error: invalid conversion from ‘long int’ to ‘char*’ [-fpermissive]
    foo(func, nullptr);   
}

foo şablon fonksiyonunun ikinci parametresine NULL verirsek derleyici f fonksiyonunu integer bir değerle çağırmaya çalışacak. Çünkü şablon parametresi olarak NULL ‘ ın türü integer tespit edildi. nullptr ile çağırdığımızda ise türü std::nullptr_t olarak tespit edileceğinden sorunsuzca ‘f’ veya ‘func’ fonksiyonu sorunsuzca çağırılacaktır.

Aslında nullptr nin implementasyonu bir const class’dır. Merak edenler için standart taslağında implementasyonu şu şekildedir:


const // this is a const object...
class {
public:
 template<class T> // convertible to any type
 operator T*() const // of null non-member
 { return 0; } // pointer...
 template<class C, class T> // or any type of null
 operator T C::*() const // member pointer...
 { return 0; }
private:
 void operator&() const; // whose address can't be taken
} nullptr = {};

Advertisements

C++11 – Zeki, Akıllı Göstericiler (Smart Pointers)

C++ programlama dili, atası C dilinden aldığı özelliklerden biri de memory yönetimidir. Ham göstericilerle (raw pointer) rahatlıkla hafızayı yönetebilirsiniz. ‘new’ operatörü ile aldığınız bellek bloğunu kolaylıkla ‘delete’ operatörüyle iade edebilriz. Fakat bu beraberinde riskleri de beraberinde getiriyor.

Memoryye iade edilmeyen yani ‘delete’ edilmeyen nesneler üzerinden bu bellek bloğuna erişilmeye çalışıldığında çalışma zamanı hatalarıyla (run time error) karşılaşıyor, signal 11 (segmentation fault) gibi kernelden uyarılar alabilirsiniz. İşte bu noktada C++’ ın en güçlü yanlarından olan RAII’ ye dayanan dinamik memory tahsisini de nesnelerin ömürlerine bağlayan sınıflar tasarlandı. Kullanım amaçlarına göre farklılık genel olarak değişse de amaç; hatalara neden olan new, delete operatörleriyle açıktan açığa bellek tahsisini kodumuzda yapmamak.

auto_ptr (Deprecated):
C++ 98 standartlarıyla beraber dile katılan şablon sınıfı auto_ptr temel olarak bellek alanlarının tahsisi ve geri iadesi üzerine odaklandı. Fakat yanlış kullanımı nedeniyle memory hatalarından kaçalım diye tasarlanan akıllı göstericilerin amacının tam tersi şekilde memory hatasına neden oldu. Aşağıdaki kodu inceleyelim.

#include <iostream>
#include <memory>

using namespace std;

struct s
{
    int a;
    int c;
    string b;

    s(){cout << "S ctor" << endl;}
    ~s(){cout << "~S dtor" << endl;}
};

void func(auto_ptr<s> e)
{
    e->a = 99;
}

int main()
{
    auto_ptr<s> aptr(new s);

    cout  << "func begin" << endl;
    func(aptr);
    cout  << "func end" << endl;      

    cout << aptr->a << endl;

}


Örnek kodda aptr nesnesi ile ‘f’ fonksiyonu çağrıldğında copy ctor çağırılacak ve aynı nesneyi gösteren bir diğer auto_ptr nesnesi oluşturulacak. Ve bu nesnenin(e) ömrü fonksiyon blokları sonlandığında sonlanacağı için gösterdiği dinamik bellek alanını da free edecek. Dolayısıyla aptr nesnesi artık free edilmiş bir bellek alanını göstermektedir kısacası dangling pointer hatasına sebep olur. Program seg fault vererek aşağıdaki şekilde sonlanır:

S ctor
func begin
~S dtor
func end

RUN FINISHED; Segmentation fault; core dumped; real time: 90ms; user: 0ms; system: 0ms

auto_ptr sınıfı C++17 standartlarıyla beraber standart kütüphaneden çıkarılacaktır. Dolayısıyla kullanmayın .)

unique_ptr
auto_ptr nin yaptığı hatayı yapmaz, tek bir sahiplik vardır. Copy ctor, operator = fonksiyonlarını =delete şeklinde yasaklar dolayısıyla kullanıcı herhangi bir yerde herhangi bir şekilde nesneyi kopyalamaya çalıştığında derleme hatası alacaktır.

Yukarıdaki örneği unique_ptr ile yeniden yazarsak derleme zamanında aşağıdaki gibi bir hata alırız:

main.cpp:29:14: error: use of deleted function ‘std::unique_ptr::unique_ptr(const std::unique_ptr&amp;) [with _Tp = s; _Dp = std::default_delete<s>]’<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>

Yani ‘deleted’ bir fonksiyon olan copy ctorı çağıramazsın. Peki bir unique_ptr yi bir fonksiyona veya bir sınıfa nasıl transfer edeceğiz, call by value yerine call by reference ile, tekrardan üstteki örnek:

#include <iostream>
#include <memory>

using namespace std;

struct s
{
    int a;
    int c;
    string b;

    s(){cout << "S ctor" << endl;}
    ~s(){cout << "~S dtor" << endl;}
};

void func(unique_ptr<s> &e)
{
    e->a = 99;
}

int main()
{
    unique_ptr<s> uptr(new s);

    cout  << "func begin" << endl;
    func(uptr);
    cout  << "func end" << endl;      

     cout << uptr->a << endl;

}

Yapıtığım tek şey ‘void func(unique_ptr)’ fonksiyon imzasını ‘void func(unique_ptr &)’ ile değiştirmek böylelikle sahiplik değişmeden unique_ptr sınıfını func fonksiyonuna parametrik olarak geçirmiş oluruz.Ayrıca std::move kullanarak da sahipliğini ‘move’ ederek değiştirebiliriz. En nihayetinde auto_ptr de karşılaştığımız bellek(memory) sorunlarından da kurtuluyoruz.

shared_ptr
Her ne kadar unique_ptr ile bellek hatalarını izole etsekte kullanım olarak hala sıkıntılarımız mevcut. Ham (raw) göstericilerdeki özellikleri olabildiğince gerçek hale getirmek akıllı göstericilerin implementasyonuna yön veren amaçlardan biri. Öyleyse bir den fazla pointerın aynı bellek alanına işaret etmesini de gerçeklemek gerekiyor.

Tam bu nokta da shared_ptr devreye giriyor. shared_ptr aslında bir referans sayacı tutuyor. shared_ptr ctor’ında başlangıç değeri olarak ‘new’ ile oluşturulan nesne verildiğinde aslında iki tane bellek tahsisi mevcut. Birincisi direkt zaten ‘new’ ile tahsis edilen nesne ikincisi ise referans sayacını kontrol eden, yöneten nesne(Manager Object). Örneğin sp1 ilk oluşturulan nesnemiz olsun içinde bir de Manager Object oluşturulacak. Diğer oluşturulan shared_ptr nesneleri için bu nesne de bulunan sayaç bir arttırılacak. Tam tersi şekilde Manager object içindeki shared sayacı ve weak sayacı sıfırlandığında ise heap bellek alanından tahsis edilen nesne free edilecek.

 

Yine aynı örnek üzerinden gidersek:

#include <iostream>
#include <memory>

using namespace std;

struct s
{
    int a;
    int c;
    string b;

    s(){cout << "S ctor" << endl;}
    ~s(){cout << "~S dtor" << endl;}
};

void func(shared_ptr<s> e)
{
    e->a = 99;
    cout << "count of shared pointer:" << e.use_count() << endl;
}

int main()
{
    shared_ptr<s> sptr(new s);

    cout  << "func begin" << endl;
    func(sptr);
    cout  << "func end" << endl;      

    cout << sptr->a << endl;

}

Görüldüğü üzere func fonksiyonunu referansla çağırmama rağmen herhangi bir bellek hatasıyla karşılaşmıyorum. Burda Manager object, shared pointer sayacını bir arttırıyor. Func fonksiyonu sonunda ise yine sayacı bir azaltıyor(Argüman olarak yaratılan ‘shared_ptr’ türünden ‘e’ nesnesinin ömrü sona erdiği için). Sonrasında process sorunsuz olarak sonlanıyor.

make_shared: Yukarıda bahsettiğimizi gibi shared pointer oluşturulduğunca 2 kez dinamik bellek tahsisi yapılır. Birisi yönetici nesne için diğeri ise T türü için. Performans açısından bu ikisini aynı anda tahsis etmek maliyetli bir işlemdir. Bu soruna standart kütüphaneden make_shared ile çözüm bulunmuş. make_shared kullanıldığında aynı bir kez bellek tahsisini hem T türü için hem de yönetici nesne için beraber yapar. Yani tek bir blok heapten alınır bu da tasarımı itibarıyla pahalı işlem olan dinamik bellek tahsisi teke düşmüş olur. Yine yukarıdaki örnekten devam edersek:

//shared_ptr<s> sptr(new s);
shared_ptr<s> sptr = make_shared<s>();

şeklinde değiştirmek yeterli olacaktır.

weak_ptr
Raw pointer aracılığıyla oluşturulamaz shared_ptr veya başka bir weak_ptr nin kopyalanması, atanmasıyla yaratılır. shared_ptr gibi çalışır ama nesnenin ömrüne müdahalede bulunmaz. Yönetici nesnede (Manager Object) sayaç sıfır dahi olsa hiçbir şekilde tahsis edilen objenin ömrüne etki de bulunmaz. Sadece okuma hakkında sahiptir. Ownership (sahiplik) sadece shared_ptr dedir. Tipik olarak nesneye erişiminizin gerektiği fakat sayacı artırmanıza gerek olmadığı durumlarda kullanılır. Ayrıca lock() fonksiyonu ile weak_ptr işaret ettiği shared_ptr elde edilebilir.

#include <iostream>
#include <memory>

using namespace std;

struct s
{
    int a;
    int c;
    string b;

    s(){cout << "S ctor" << endl;}
    ~s(){cout << "~S dtor" << endl;}
};

void func(shared_ptr<s> e)
{
    e->a = 99;
    cout << "count of shared pointer:" << e.use_count() << endl;
}

int main()
{
    shared_ptr<s> sptr = make_shared<s>();
    {
        //weak_ptr<s> wptr (new s);   error: main.cpp:29:33: error: no matching function for call to ‘std::weak_ptr<s>::weak_ptr(s*)’
        weak_ptr<s> wptr = sptr;
        cout << "count of weak pointer:" << wptr.use_count() << endl;
    }

    cout  << "func begin" << endl;
    func(sptr);
    cout  << "func end" << endl;      

    cout << sptr->a << endl;

}

enable_shared_from_this : weak_ptr ‘ nin kullanıldığı yerlerden biridir. Boost kütüphanesinin C++ diline kazandırdığı bir diğer özellikdir. Örneğin bir üye fonksiyondan başka bir fonksiyona ‘this’ pointerını geçmek istediğinizde eğer nesne shared_ptr tarafından kontrol ediliyorsa bir diğer shared_ptr objesi yaratmak durumunda kalacaksınız. Bu da yine dangling pointer ‘a sebep olacak. Örneğin:

void printb (struct s *ps)
{
    cout << b << endl;
}

struct s
{
    int a;
    int c;
    string b;

    s(){cout << "S ctor" << endl;}
    ~s(){cout << "~S dtor" << endl;}
    void printB()
    {
        printb(this);

    }
};

Burda görüldüğü üzere aynı bellek alanını iki adet manager object yönettiğinde herhangi birinin sayacı sıfırlandığında gösterdiği obje free edileceğinden yine bir dangling pointer olma durumu mevcut. Bunu engellemek için sınıf içerisinde bir tane weak_ptr barındırarak nesnenin ömrüne etki etmiyoruz. Bunu da türetme yöntemiyle şu şekilde implemente ediyoruz:

void printb (shared_ptr ps);
struct s : public enable_shared_from_this <s>
{
    int a;
    int c;
    string b;

    s(){cout << "S ctor" << endl;}
    ~s(){cout << "~S dtor" << endl;}
    void printB()
    {
        shared_ptr<s> sptr = shared_from_this(); // burda ikinci bir manager object üretilmiyor, sadece shared_ptr counter 1 arttırılıyor.
        printb(sptr);

    }
};

void printb (shared_ptr<s> ps)
{
    cout << ps->b << endl;
}

Her ne kadar smart pointers size daha güvenli bir kullanım sunsa da yine de dikkat edilmesi gereken yerler var. Özellikle unique_ptr, shared_ptr ve weak_ptr şablon sınıflarının kullanımlarının iyi bir şekilde öğrenilmesi ve uygulamayla pratik yapılmasında fayda var. He bir de auto_ptr ‘ yi aklınıza bile getirmeyin 🙂

C++11 – Derleme zamanında tür belirleyiciler (type traits)

Basitçe derleme zamanında tür hakkında bilgi edinmemizi sağlarlar. Kullanımları oldukça basittir:


#include <iostream>
#include <memory>
#include <type_traits>

using namespace std;

class A
{
public:
    ~A(){cout << "~A()" << endl;}
     A& operator=(const A&) { return *this; }
};

int main() 
{    
    cout << boolalpha << endl;
    
    cout << is_object<int>::value << endl;
    cout << is_object<int&>::value << endl;
    cout << is_object<A>::value << endl;
    cout << is_object<A&>::value << endl;
    
    cout << "-----------------------------" << endl;
    
    struct B {};
 
    enum class C {};   
    
    cout << is_class<A>::value << endl;
    cout << is_class<B>::value << endl;
    cout << is_class<C>::value << endl;
    cout << is_class<int>::value << endl;    
    
    cout << "-----------------------------" << endl;
    
    function<void()> f = [](){cout << "is function object move assignable ?" << endl;};

    cout << is_move_assignable<B>::value << endl;
    cout << is_move_assignable<C>::value << endl;
    cout << is_move_assignable<decltype(f)>::value << endl;
    cout << is_move_assignable<A>::value << endl;
 
}

Başka bir kullanım alanları ise şablon (template) fonksiyonları için bir nevi koşul veya kısıtlama getirmek için de kullanılırlar. Tipik olarak std::enable_if ile kullanılırlar, std::enable_if temel olarak SFINAE (Substitution failure is not an error) prensibine dayanır. Kısacası derleyici, şablon fonksiyon overloadlarını yazarken oluşan hatayı görmezden gelir ve diğer fonksiyonları yazmaya devam eder., Örneğin:

#include <iostream>
#include <memory>
#include <type_traits>
using namespace std;

class A
{   
public:
    
    friend ostream &operator<<(ostream &os, const A&) 
    {
        os << "class A" << endl; 
    }
};

template <class T>
typename enable_if<is_integral<T>::value, T>::type
println(T a)
{
    cout << "if T is a integral type: " << a << endl;
};


template <class T>
typename enable_if<is_object<T>::value, T>::type
println(T &a)
{
    cout << "if T is a object type: " << a << endl;
};

int main(int argc, char**argv) 
{
    
    A a;
    
    println(5);
    println(a);
    
    return 0;
}

Örnekte görüldüğü üzere println fonksiyonu iki farklı şekilde şablonu üretiliyor. “enable_if::value, T>::type” ifadesi ile sadece istenen türlerle o fonksiyonun çağrılmasına olanak veriyor. is_object aslında derleme zamanında gelen türün nesne mi olup olmadığını test etmemizi sağlıyor. Böylelikle kullanıma göre derleme zamanında uygun şablon fonksiyonlar yazılacak.

type traitslerin aşağıdaki linkte bir listesini bulabilirsiniz:

http://en.cppreference.com/w/cpp/types

C++11 – std::tuple üzerine

STL’ i kullananlar bilirler genelde iki farklı türden anlamsal birliktelikleri olan değişkenleri tutmak için genelde std::pair kullanılırdı.

pair<int, string> pt (1, "herbibk");
cout << pt.first << "-" << pt.second << endl;

C++11 ile gelen veri yapılarından biri olan std::tuple’ la birlikte bu özellik sınırsız türde nesneyi sınırsız sayıda içermeye kadar gitti. Üstelik derleme zamanında veri yapısındaki nesne sayısını da biliyorsunuz.

Aslında tuple’ ın nasıl implemente edildiğine baktığınızda basitçe C++’ ın iki temel özelliğini kullanılmış; Çoklu Türetme (Multiple Inheritance) ve Değişken parametreli şablonlar (variadic templates).

template <typename T>
struct tuple_leaf
{
T value_;
};

template <typename... Types>
class tuple : tuple_leaf<Types>...
{
};

‘tuple_leaf…’ parametre pack’i (türkçesini daha düşünmedim) ifadesi tuple_leaf, tuple_leaf, tuple_leaf….tuple_leaf şeklinde açılacak böylelikle tuple sınıfı da bunlardan çoklu türetmeyle türetilecek.

Fakat burda şöyle bir sıkıntı var, aynı türde nesneler geldiğinde örneğin T1 ve T3 string olduğunda bu yapı çalışmayacak zaten var olan bir sınıf için ikinci defa bir sınıf yazılmayacak ve derleme hatası verilecektir. STL de bu sıkıntıyı aşmak için std::integer_sequence kullnılmış derleme zamanında indis üretmek için. Böylelikle aynı tür nesneler geldiğin de bile indisleri farklı olduğundan farklı bir sınıftan türetiliyormuş gibi olacak. Biraz zorlama bir trick olmuş .)

Peki daha önceki variadic template fonksiyonlarından bahsettiğimiz yazılarımdaki1 gibi recursive bir tanım yaparsak bu sorunları aşabilir miyiz ?

template <class... Ts> struct tuple {};

template <class T, class... Ts>
struct tuple<T, Ts...> : tuple<Ts...> {
tuple(T t, Ts... ts) : tuple<Ts...>(ts...), tail(t) {}

T tail;
};

Yukarılda görüldüğü gibi rekürsiv olarak her bir tür için bir sınıf diğer kalan türlerden türetilerek oluşturulacak.

Aslında şöyle bir yapı ortaya çıkacak;

struct tuple<T1, T2, T3> : tuple<T2, T3> {
T1 tail;
}

struct tuple<T2, T3> : tuple<T3> {
T2 tail;
}

struct tuple<T3&>: tuple {
T3 tail;
}

struct tuple {
}

Rekürsiv olarak tanımlamak biraz kafa karıştırsa da en kısa yol. Standart kütüphaneye bakıldığında bu işi non-recursive yoldan halletmişler. Tuple implementasyonunu meta programming bakış açınızı güçlendirmek adına inceleyebilirsiniz.

Göz at:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf
http://blogs.microsoft.co.il/sasha/2015/01/12/implementing-tuple-part-1/

C++11 – Üye fonksiyonlarda ‘default’ ve ‘delete’ manipülasyonu

C++03 standartlarına göre boş bir sınıf tanımlasanız bile derleyici tarafından bir tane default constructor, copy ctor(constructor) ve atama operatörünü (operator=) sizin için oluşturuyordu.

C++11 standartlarıyla beraber direkt olarak derleyicinin bu tarz davranşlarını manipüle etme şansına hakim oluyorsunuz. C++ programlama dilini işte en çok bu yüzden seviyorum, yazılımcıya olabildiğince fazla hareket alanı sağlıyor.

Tipik bir örnek olarak eğer sınıfınıza herhangi bir ctor tanımladığınızda derleyici sizin için ctor tanımlamayacaktır. Aşağıdaki kodu inceleyelim;


#include <iostream>

using namespace std;

class herbibk
{
public:
    herbibk(int a) {};
};

int main(int argc, char**argv)
{

    herbibk hbk(5); //ok
    herbibk hb; //hata derleyici default ctor oluşturmadığı için:
                      //main.cpp:23:13: error: no matching function for call to ‘herbibk::herbibk()’

    return 0;
}

C++11 ile gelen yeni özellikle beraber derleyiciye kardeşim sen yine bana default ctor üret diyebiliyoruz;

#include <iostream>

 
using namespace std;
 
class herbibk
{
public:
    herbibk(int a) {};
    herbibk()  = default;
};
 
int main(int argc, char**argv) 
{
 
    herbibk hbk(5); //ok    
    herbibk hb; //ok
    

 
    return 0;
}

Görüldüğü üzere artık derlerken hata almıyoruz.

herbibk()  = default;

İfadesini sadece sınıfın tanımına ekleyerek derleyici tarafından oluşturulan default ctor’un her zaman oluşturulmasını sağladık.

Başka bir kullanımı ise, daha önce C++03 zamanıda : ) bir sınıfı kopyalanamaz yapmak için copy ctor’unu ve atama operatörünü private yapılırdı. Şimdi bizim ‘delete’ imiz var hatta operator new’ i de ‘delete’ edebiliriz, Şöyle ki;

#include <iostream>

 
using namespace std;
 
class herbibk
{
public:
    herbibk(int a) {};
    herbibk()  = default;
    herbibk(const herbibk&) = delete;
    herbibk& operator=(const herbibk&) = delete; 
    void* operator new(std::size_t) = delete;
    void* operator new[](std::size_t) = delete; 
};
 
int main(int argc, char**argv) 
{
 
    herbibk hbk(5); //ok    
    herbibk hb(hbk); //hata  derleyiciye copy ctor fonksiyonunu oluşturma demiştik : main.cpp:21:19: error: use of deleted function ‘herbibk::herbibk(const herbibk&)’
    
    hb = hbk; // hata derleyiciye atama operatörü fonksiyonunu oluşturma demiştik 🙂  : main.cpp:23:8: error: use of deleted function ‘herbibk& herbibk::operator=(const herbibk&)’
    
    herbibk *pHbk = new herbibk[4]; // hata main.cpp:25:34: error: use of deleted function ‘static void* herbibk::operator new [](std::size_t)’
    herbibk *pHbk = new herbibk; // hata main.cpp:26:25: error: use of deleted function ‘static void* herbibk::operator new(std::size_t)’

    return 0;
}

‘delete’ anahtar kelimesi otomatik tür dönüşümünü engellemek amacıyla tüm üye fonksiyonlara uygulanabilir, Örneğin;

#include <iostream>

 
using namespace std;
 
class herbibk
{
public:
    herbibk(int a) {};
    herbibk()  = default;
    herbibk(const herbibk&) = delete;
    herbibk& operator=(const herbibk&) = delete;
    void f(int a) {};
    void f(double &) = delete;
};
 
int main(int argc, char**argv) 
{
 
    herbibk hbk(5); //ok    
    herbibk hb;
    
    hb.f(5); // ok sorun yok
    hb.f(5.); // ok sorun yok, çünkü otomatik olarak 5.0 int türüne dönüştürüldü.
    
    return 0;
}

Ayrıca bu kullanımı fonksiyon şablonlarını (function template) kullanarak genelleştirebiliriz; Örneğin f fonksiyonunu integer türü haricindeki hiçbir argümanla çağıramayalım:

#include <iostream>

 
using namespace std;
 
class herbibk
{
public:
    herbibk(int a) {};
    herbibk()  = default;
    herbibk(const herbibk&) = delete;
    herbibk& operator=(const herbibk&) = delete;
    void f(int a) {};
    template<typename T> void f(T) = delete;
};
 
int main(int argc, char**argv) 
{
 
    herbibk hbk(5);  
    herbibk hb;
    
    hb.f(5); // ok sorun yok 
    hb.f(5.00); // hata: main.cpp:26:14: error: use of deleted function ‘void herbibk::f(T) [with T = double]’
    
    return 0;
}

Standart string kütüphanesi fonksiyonlarında geri dönüş değeri olarak “unsigned int”

Birçok yerde denk geldiğim için bununla ilgili bir blog yazısı yazmanın iyi olacağını düşünüyorum. Şimdi aşağıdaki koda bir bakmanızı rica ediyorum, ekrana ne yazılmasını bekliyorsunuz ?

#include    <iostream>
#include    <string>

using namespace std;

int main()
{
    string s("cppturkey");

    unsigned int index = s.find_last_of(".");

    if (index == string::npos)
        cout << "Nokta namevcut" << endl;
    else
        cout << "Nokta mevcut" << endl;

    return 0;

}

Aslında bu sorunun bir cevabı yok, sistemden sisteme değişebilir. string operasyonları geri dönüş değeri “size_t” türündendir. size_t ise standartlarda mevcut sistemin en yüksek pozitif alabileceği değerdir(pozitif -1). Aşağıdaki örneğe göz atabilirsiniz.


#include <iostream>
#include <string>

using namespace std;

int main()
{
unsigned int x = -1;
size_t y = -1;

cout << x << endl;
cout << y << endl;
return 0;

}

 

Türler arasındaki bağlantı C++ standartların aşağıdaki şekilde tanımlanmıştır ve garanti altındadır:

sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) = sizeof(size_t) <= sizeof(long long)

Dolayısıyla kullandıgınızda sistemde sizeof(unsigned int), sizeof(size_t)'dan farklı olabilir. Küçük olduğu durumlarda integer overflow olduğundan 'index == string::npos' ifadesi doğru olmayacak ve ekrana "Nokta mevcut" yazacaktır.

Garantili yol ise index değişkenini size_t türünden;

size_t index = s.find_last_of(".");

şeklinde tanımlamak olacaktır.

Smart Pointers History – from 03 to 17

Smart pointerların anlatıldıgı C++03 ten C++17 standartlarına kadar nelerin değiştiğinin anlatıldıgı söyleşimize herkes davetlidir.

Smart Pointers – from 03 to 17

Friday, Mar 27, 2015, 3:00 PM

Yıldız Teknopark
İkitelli- Başakşehir Istanbul, TR

2 C++’ers Went

Kat 1 No: 101

Check out this Meetup →

Macro’s vs Variadic Templates – 2

Former article was visited by C++ fans from around the world. Therefore i decided to publish my article in english. Thanks for visiting web site.

More Generic Design
You know templates provide more generic design than function overloading. And now with C++11, you can even design more generic templates than templates, so variadic templates.

I want to write a function that gives me size of all containers in STL(even if they almost all have a size function 🙂 ). You know STL has two main container types:
– Sequence Containers
– Associative Containers

Sequence containers take 2 arguments ValueType and mainly Allocator:

template<class T,
         class Allocator = std::allocator<T>
         > class SeqContainer;

I will try to implement my function-template for sequence containers.

template < template <typename, typename> class ContainerType, typename ValueType, typename Alloc>
size_t sizeOfContainers(ContainerType<ValueType, Alloc> &c)
{
    size_t s = 0;
    for (auto element : c) {
        s++;
    }
    return s;
}

As you see first template parameter “template class ContainerType” is a template class as template parameter. Second and third one are template parameters of first parameter.

But that function template will not work for std::array type. We will look that problem later. Mainly this function satisfies our needs.

#include <vector>
#include <iostream>
#include <memory>
#include <map>
#include <forward_list>
#include <unordered_set>
#include <array>

using namespace std;

template < template <typename, typename> class ContainerType, typename ValueType, typename Alloc>
size_t sizeOfContainers(ContainerType<ValueType, Alloc> &c)
{
    size_t s = 0;
    for (auto element : c) {
        s++;
    }
    return s;
}



int main()
{
    vector<int> v({1, 4, 5, 3, 5, 6});
    forward_list<int> fl ({ 34, 99, 10, 71});
    
    cout << sizeOfContainers(v) << endl;
    cout << sizeOfContainers(fl) << endl;

   
  return 0;
}

Associative containers take 3 or 4 arguments for example:

std::set

template<
    class Key,
    class Compare = std::less<Key>,
    class Allocator = std::allocator<Key>
> class set;

std::map

template<
    class Key,
    class T,
    class Compare = std::less<Key>,
    class Allocator = std::allocator<std::pair<const Key, T> >
> class map;

std::unordered_set

 
template<
    class Key,
    class Hash = std::hash<Key>,
    class KeyEqual = std::equal_to<Key>,
    class Allocator = std::allocator<Key>
> class unordered_set;

Therefore we need two more overload for our function to handle this containers. Let’s try to write them :


template < template <typename, typename, typename> class ContainerType, typename FirstType, typename SecondType, typename AllocType>
size_t sizeOfContainers(ContainerType<FirstType, SecondType,  AllocType> &c)
{
    size_t s = 0;
    for (auto element : c) {
        s++;
    }
    return s;
}

template < template <typename, typename, typename, typename> class ContainerType, typename FirstType, typename SecondType, typename ThirdType, typename AllocType>
size_t sizeOfContainers(ContainerType<FirstType, SecondType, ThirdType, AllocType> &c)
{
    size_t s = 0;
    for (auto element : c) {
        s++;
    }
    return s;
}

As you see for that functions can handle this issue for different type.

But now we have three overloads, can we reduce it to one function with variadic functions ? Lets try it:

template <template <typename, typename...> class ContainerType, typename FirstType, typename... Types> 
size_t sizeOfContainers(ContainerType<FirstType, Types...> &c)
{
    size_t s = 0;
    for (auto element : c) {
        s++;
    }
    return s;
}

Now we can handle all 2,3,4 parameter containers. Except one std::array. std::array takes two parameter:

template< 
    class T, 
    std::size_t N 
> struct array;

Remember, we discussed variadic template function creation at first article. In that example our variadic function will not create function that we need for std::array. Actually second type of std::array is not a type, it is a constant. And it is not allowed in C++, also compile time error. Function overloading take responsibility for us :

template <template <typename, size_t> class ContainerType, typename FirstType, size_t S> 
size_t sizeOfContainers(ContainerType<FirstType, S> &c)
{
    size_t s = 0;
    for (auto element : c) {
        s++;
    }
    return s;
}

That overload will handle our issue. Sometimes we need partial specialization overloads to resolve problems.

At the end of the day score is on the board:

Variadic templates 3 – 1 Function overloading

Full Source Code:

#include <vector>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <map>
#include <forward_list>
#include <unordered_set>
#include <array>
#include <set>
#include <deque>

using namespace std;


template <template <typename, typename...> class ContainerType, typename FirstType, typename... Types> 
size_t sizeOfContainers(ContainerType<FirstType, Types...> &c)
{
    size_t s = 0;
    for (auto element : c) {
        s++;
    }
    return s;
}


template <template <typename, size_t> class ContainerType, typename FirstType, size_t S> 
size_t sizeOfContainers(ContainerType<FirstType, S> &c)
{
    size_t s = 0;
    for (auto element : c) {
        s++;
    }
    return s;
}

template < template <typename, typename> class ContainerType, typename ValueType, typename Alloc>
size_t sizeOfContainers(ContainerType<ValueType, Alloc> &c)
{
    size_t s = 0;
    for (auto element : c) {
        s++;
    }
    return s;
}

template < template <typename, typename, typename> class ContainerType, typename FirstType, typename SecondType, typename Alloc>
size_t sizeOfContainers(ContainerType<FirstType, SecondType,  Alloc> &c)
{
    size_t s = 0;
    for (auto element : c) {
        s++;
    }
    return s;
}

template < template <typename, typename, typename, typename> class ContainerType, typename FirstType, typename SecondType, typename ThirdType, typename Alloc>
size_t sizeOfContainers(ContainerType<FirstType, SecondType, ThirdType, Alloc> &c)
{
    size_t s = 0;
    for (auto element : c) {
        s++;
    }
    return s;
}



int main()
{
    vector<int> v({1,2,3,4,5,6});
    map<int, int> m({ {2,3},  {4, 5}});
    set<int> s({8, 7});
    deque<int> d = {10,20,30};
    forward_list<int> fl ({ 34, 77, 16, 2 });
    array<int,10> a = { 2, 16, 77, 34, 50};
    unordered_set<string> us = {"C++", "UG","ISTANBUL","69736c616d"};
    

    cout << sizeOfContainers(v) << endl;
    cout << sizeOfContainers(m) << endl;
    cout << sizeOfContainers(fl) << endl;
    cout << sizeOfContainers(us) << endl;
    cout << sizeOfContainers(s) << endl;
    cout << sizeOfContainers(a) << endl;
    cout << sizeOfContainers(d) << endl;
    
  return 0;
}

Debugging with GDB – C++ UG Meetup

28 Kasım Cuma günü saat 15:00’de “Debugging with GDB” adlı seminerimize herkes davetlidir.

C++ User Group Istanbul-cppturkey.org

Istanbul, TR
378 C++’ers

C++ UG GROUP ISTANBUL- aimed at sharing knowledge and experience

Check out this Meetup Group →

Macro’s vs Variadic Templates – 1

Variadic fonksiyonlar istenilen sayıda değişken almaları sayesinde bir çok işi kolaylaştırmışlardır. C++11 den önce C de bu ihtiyaç “…” elipsis operatörü ve va_* makrolarıyla çözülüyordu. Fakat bu çözüm çalışma zamanında işlediğinden yanlış kullanım veya beklenmedik durumlarda çalışma zamanı hataları veya seg fault dahi alınabiliyordu.

Örneğin;

int a ;

printf("%s", a);

Bu kod örneği derleme zamanında (compile time) hata vermez. Fakat çalışma zamanında seg fault alırsınız. C++11 de bu problem variadic templatelerle çözülüyor. Üstelik derleme zamanında çözüldüğü için yanlış kullanım durumunda hemen müdahale edebiliyorsunuz.

Variadic templates

Basit bir önekle devam edelim;

#include <iostream>

using namespace std;

template<typename T>
void println(T v) //A
{
    cout << v << endl;
}

template<typename T, typename... Types>
void println(T first, Types... values) //B
{
    println(first);
    println(values...);
}

int main()
{
   println("islam", "cppistanbul", 1299); //1
   println(6, 1, 0, "c++UG", 5, 7, 1); //2
  
  return 0;
}

“typename… Types” şablon parametre paketi(template parameter pack), “Types… values” ise fonksiyon parametre paketi (function parameter pack) şeklinde adlandırılıyor. template parameter packteki Types aslında bir tür listesi. Örneğin “1” durumunda bu liste “const char *, const char *, integer” şeklindedir.Function parameter pack ise bir değer listesidir. Yine “1” durumunda bu liste “islam, cppistanbul, 1299” şeklindedir. Elipsis (…) operatörüyle de bu değerler ve türler açılır(expand). Burda dikkat ederseniz iki tane println fonksiyonu var, basitçe bir oveload aslında. Bu overload ile aslında kısmi bir rekürsiv çağrı yapıyoruz. Daha iyi anlaşılması için adım adım ilerleyelim.

1.iterasyon:

“1” durumunda println fonksiyonu 3 tür ie çağırılmış. Template argument deduction yaparak B deki overload çağrılacak ve:

void println(T, Types ...) [with T = const char*; Types = {const char*, int}]

parametre bu şekilde belirlenecek. Derleyici bir tane yukarıdaki imzaya sahip bir fonksiyon oluşturacak. Ve dikkat edin A durumundaki tek parametreli template de şu şekilde derleyici tarafından yazılacak:

void println(T) [with T = const char*]

2. iterasyon:

B template fonksiyonu:

void println(T, Types ...) [with T = const char*; Types = {int}] 

şeklinde, A template fonksiyonu ise yine aynı imzaya sahip olacağından tekrar yazılmayacak.

3. iterasyon

Bu durumda ise sadece tek tür kaldıgından direkt olarak A template fonksiyonu tercih edilecek(argument deduction). Ve aşağıdaki imzaya sahip bir fonksiyon derleyici tarafından yazılacak:

void println(T) [with T = int]

Bu sayede println fonksiyonuna verdiğimiz değerlere uygun overload edilmiş println fonksiyonu çağıralarak ekrana bu değerler yazılmış olacak.

To be continued :))