C++ Trait Sınıfının Çıkış Hikayesi

oğuzhan katlı
4 min readOct 2, 2019

--

github.com/nixiz/tql

C++ dilinin Bjarne Stroustrup tarafından 1979 yılında Cambrigde üniversitesinde doktora tezi(“C with Classes”) olarak sunulmasının ardından, 1998 yılında uluslararası bir standart haline getirilene dek bir çok problemin çözülmesi ve yeni tekniklerin geliştirilmesi gerekmekteydi. Bu süreçteki en önemli gelişmelerden biri, 1995 yılında Nathan C. Myers’ın trait sınıflarından bahsettiği makalesinin[1] yayınlanması ile gerçekleşmiştir. Bu tekniğin geliştirilmesindeki sebeplerden bir tanesi, karakterler ile çalışan kütüphanelerin karakter tipine göre özelleştirilmelerine ihtiyaç duyulmasıydı.

Not: Makalede bahsedilen karakter tipi farklılığı, ASCII ve unicode farklığını anlatmaktadır. Günümüzde kullanılan karakter tipleri: char, wchar_t ve C++11 ve sonrasında gelen char16_t, char32_t ve char8_t tipleridir.

Myers, makalesinde halihazırda bulunan iostream kütüphanesinin bu özelleştirmeleri yapabilmesinin çok zor olduğunu, çözümü için geliştirdiği yeni tekniğin de beklenmedik şekilde başarılı ve yaygın olarak kullanılabileceğinden bahsetmektedir.

Trait tekniği hızla kabul görmüş ve C++ standart kütüphane için olmazsa olmaz hale gelmiştir; C++11 standardı ile gelen type_trait kütüphanesi de bu teknik ile geliştirilmiştir.

Teknik, getirdiği imkanlar sayesinde yeni bir çağın başlamasında büyük bir rol oynamaktadır.

<iostream> Kütüphanesinin Genelleştirilmesi

iostream kütüphanesi içerisinde yer alan streambuf sınıfı, akış kontrolünü sağlamak için EOF(End Of File) değerini kullanmaktadır. EOF değeri diğer karakter değerlerinden farklı olarak tanımlanmaktadır ve bu özel değişken ile akışların sonlandırılması mümkün olmaktadır.

class streambuf {
...
int sgetc(); // bir sonraki değeri ya da EOF döner
int sgetn(char*, int N); // N adet karakter döner
};

Peki, streambuf sınıfının karakter tipine göre özelleştirilmesi için ne yapılmalıdır? Karakter tipiyle beraber, EOF değişkeni de karakter tipine göre özelleştirilmelidir. Bunun için karakter tipi ve sgetc() metodunun dönmesi gereken değişkenin tipi de genelleştirilmelidir:

template <class charT, class intT>
class basic_streambuf {
...
intT sgetc();
int sgetn(charT*, int N);
};

EOF değişkeninin özelleştirilmesi için eklenen ikinci şablon parametresi işleri biraz karmaşıklaştırmaktadır. iostream kütüphanesini kullanan kişiler EOF kavramının ne olduğunu bilmeyebilirler ve bilmek zorunda da değillerdir. Hatta, streambuf sınıfı EOF değişkeni olarak hangi değeri dönmesi gerektiğini de bilmemektedir; EOF değişkenini de ayrı bir şablon parametresi olarak vermek, işleri bir hayli zorlaştıracaktır. Bu şekilde yapılacak bir genelleştirmenin getireceği zorluklar ve geliştirme maliyetinin büyüklüğü katlanılamaz olacaktır.

Çözüm olarak “Trait” Tekniği

Trait tekniğini gerçekleştirmek için asıl sınıfın yanında, o sınıfa hizmet sunacak ikinci bir şablon sınıf tanımlanmaktadır.

template <class charT>
struct ios_char_traits {};

Bu yardımcı trait sınıfı varsayılan olarak boş tanımlanır*; çünkü bilinmeyen bir karakter tipi için öngörüde bulunamayız(* bu kural, tasarım kararına göre istenildiği gibi değiştirilebilir). Fakat, tanımlı tipler için bu trait şablon sınıfını özelleştirilerek, o tipe ait değişkenler belirleyebilir, ihtiyaca göre işlevler ekleyebiliriz.

Myers, makalesinde char ve wchar_t karakter tipleri için streambuf sınıfının özelleştirilmesini örnek olarak göstermiştir.

Char Tipi Özelleştirmesi

template <>
struct ios_char_traits<char> {
typedef char char_type;
typedef int int_type;
static inline int_type eof() { return EOF; }
};

Wchar_t Tipi Özelleştirmesi

template <>
struct ios_char_traits<wchar_t> {
typedef wchar_t char_type;
typedef wint_t int_type;
static inline int_type eof() { return WEOF; }
};

Not: Trait sınıflarında, sınıfa ait üyelerin olmaması, bunun yerine sadece tanımlamaların olduğunu belirtmek gerekir.

Streambuf Sınıfının Genelleştirmesi

Farklı karakter tipleri için özelleştirilen trait sınıflarını kullanan streambuf sınıfı aşağıdaki gibi gözükecektir:

template <class charT>
class basic_streambuf {
public:
typedef ios_char_traits<charT> traits_type;
typedef typename traits_type::int_type int_type;
int_type eof() { return traits_type::eof(); }
...
int_type sgetc();
int sgetn(charT*, int N);
};

Yeni tasarım ile genelleştirilme öncesindeki streambuf sınıfı arasında, sınıf içerisinde belirtilen tip tanımlaması dışında bir değişiklik olmadığını söyleyebiliriz. Genelleştirilme için istenilen şablon parametresi sayısının bir tane olması, geliştiricilerin sadece karakter tipini belirterek iostream kütüphanesindeki sınıfları kolaylıkla kullanmasına imkan sağlamaktadır.

// http://www.cplusplus.com/reference/streambuf/streambuf/sgetc
#include <iostream> // std::cout, std::streambuf
#include <fstream> // std::ifstream
#include <cstdio> // EOF

int main () {
std::ifstream istr ("test.txt");
if (istr) {
std::basic_streambuf<char> * pbuf = istr.rdbuf();
do {
char ch = pbuf->sgetc();
std::cout << ch;
} while ( pbuf->snextc() != EOF );
istr.close();
}
return 0;
}

Yeni geliştirilen “trait” tekniği ile, yukarıda bahsedilen zorlukların aşılması ve farklı tiplere göre sınıfların özelleştirilmesi çok daha kolay hale getirilmiştir. Aynı şekilde, kullanıcıların her zaman değiştirmek istemeyeceği özellikler, bu teknik sayesinde saklanabilmektedir.

Myers, makalesinde ayrıca rasyonel tipler için epsilon değerlerin tutulması ve string sınıflarının farklı karakter tipleri ile karşılaştırılması gibi farklı alanlarda da trait tekniğinin kullanımına ait örnekler vermiştir. Trait sınıfların şablonlar için son derece kullanışlı olduğundan bahseden Myers, bu tekniğin getirdiği dolaylı yoldan çözüm[2] imkanının bütün problemler için geçerli olduğunu belirtmektedir.

Trait tekniği isminin nereden geldiği sorusuna makalenin sonunda cevap veren Myers: ilk başta tekniğin adını baggage olarak tanımladığını ancak, standart geliştirme komitesi tarafından bu ismin beğenilmeyerek trait sınıflar olarak değiştirildiğinden bahsetmektedir.

[1] — Nathan C. Myers — Traits: a new and useful template technique

[2] — Level of Indirection solves every Problemhttps://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering

--

--