C++ Bazı İpuçları ve Püf Noktaları

Halen popüler ama eski nesil diller nelerdir denilince ve özellikle karizmatik olanları saymak gerekirse önce C, sonrasında C++ programlama dilleri akla geliyor. Düşük seviye dillerden olan C++, atası olan C dili üzerine aldığı gelişmelerden oluşturulmuştur ve özellikle sistem ve oyun programlamalarda çok tercih edilen bir dildir.

C++ dili aslında yeni nesil dillere göre kıyaslandığında biraz daha ağır kaçmakta, öyle bir kaç video ve ders izlemekle öğrenmek mümkün olmamaktadır. Ancak öğrenme aşamasında veya ileri durumlarda işinize yarayabilecek bazı C++ ipuçları paylaşmak istiyoruz.

try-catch fonksiyonlarla kullanımı

Bilindiği gibi bir çok dilde standart olarak hata yakalama ve bilgilendirme olarak kullanılan try-catch ifadesi fonksiyon seviyesinde de kullanılabilmektedir.

Genel yapı şu şekildedir

bool foobar()
try
{
   std::cout << "foobar" << std::endl;
   throw std::exception("foobar hatalı");
}
catch (std::exception const & e)
{
   std::cout << e.what() << std::endl;
   return false;
}

int main()
{
   auto f = foobar();
   std::cout << std::boolalpha << f << std::endl;
}

Bunu bir örnekle yapmak istersek;

bool foobar()
try
{
   // Birşeyler yap ve işlem başarılı ise sonucu "true" olarak döndür
   return true;
}
catch (std::exception const & e)
{
   std::cout << e.what() << std::endl;
   return false;
}

Bunu fonksiyon içerisinde aşağıdaki şekilde de yazmak mümkündür;

bool foobar()
{
   try
   {
      // Birşeyler yap ve işlem başarılı ise sonucu "true" olarak döndür
      return true;
   }
   catch (std::exception const & e)
   {
      std::cout << e.what() << std::endl;
      return false;
   }
}

Fonksiyon içinde try-catch kullanımı mantıklı olmakta ve özellikle constructor‘lar için dizayn edilmiştir. Ancak constructor ile olası bir problem görülebilmekte zaman zaman. Şöyle ki; Constructor tam constructor olmayan bir objeyi (object) dışa çıkarırsa (throw) (ki constructor verilen işlemi başarıyla sonuçlandıramamıştır) obje için oluştururlan destructor çağrılamamaktadır. Sonuç olarak bellek taşması oluşur.

Örnek vermek gerekirse;

struct foo
{
   foo() { std::cout << "foo constructed" << std::endl; }
   ~foo() { std::cout << "foo destroyed" << std::endl; }
};

struct bar
{
   bar() { throw std::exception("error in bar!"); }
};

struct foobar
{
   foobar()
      : m_foo(new foo()),
        m_bar()        
   {
      std::cout << "foobar constructed" << std::endl;
   }

   ~foobar()
   {
      std::cout << "foobar destroyed" << std::endl;
   }

private:
   foo* m_foo;
   bar m_bar;
};

int main()
{
   try
   {
      foobar fb;
   }
   catch (std::exception const & e)
   {
      std::cout << e.what() << std::endl;
   }
}

Bu aşağıdaki sonucu verecektir.

foo constructed
error in bar!

foo objesini başarıyla tahrip edebilmek (destroy) için try-catch aşağıdaki gibi kullanılabilir.

foobar()
  try
  : m_foo(new foo()),
    m_bar()
{
  std::cout << "foobar constructed" << std::endl;
}
catch (...)
{
  delete m_foo;
  throw;
}

Bu durumda kodun çıktısı şu şekilde olacaktır;

foo constructed
foo destroyed
error in bar!

try-cacth yerine akıllı pointer kullanımı da mümkündür. Aşağıdaki kod da aynı sonucu verecektir.

struct foobar
{
   foobar()
      : m_foo(std::make_unique<foo>()),
        m_bar()
   {
      std::cout << "foobar constructed" << std::endl;
   }

   ~foobar()
   {
      std::cout << "foobar destroyed" << std::endl;
   }

private:
   std::unique_ptr<foo> m_foo;
   bar m_bar;
};

İsimsiz “namespace” kullanımı

 

Namespace deyimi kullanımına popüler programlama dillerinde artık çok sık rastlıyoruz. Türkçeye “isim uzayı” veya “isim alanı” olarak tercüme edilen namespace deyiminin C++ dilinde genel kullanımı aşağıdaki şekildedir:

namespace namespaceAdi
{
     /*
     * sınıflar
     * fonksiyonlar
     * değişkenler
     */
}

C++ ile detaylı namespace kullanımı için http://www.kazimsoylu.com/cpp/cpp-da-namespace-kullanimi.html ziyaret edebilirsiniz. Burada konumuz isim verilmeden namespace terimi (unnamed namespace veya anonymous namespace) nasıl ve neden kullanılır ona bakacağız.

Örneğin aşağıda namespace bloğunda her hangi bir isim görmüyoruz.

namespace {
    int foo() { return 0xf00; }
}

Yukarıdaki ifade aslında aşağıdaki ifadeye eşdeğerdir.

static int foo() { return 0xf00; }

Adından da anlaşılacağı gibi namespace bir isim vermeden tanımlanır ve sadece dahili bir bağlantı sunmak için kullanılır. Yani isimsiz bir namespace tanımı içindeki ifadeler sadece o kod dosyasında kalır, diğer dosyalardakilerle karışmaz. Daha iyi anlamak için bir örnek verelim.

foo.cpp ve bar.cpp kod dosyalarında tanımlanmış aynı isimde print fonksiyonları yazdığımız düşünelim.

// foo.cpp
void print(std::string message)
{
   std::cout << "[foo]" << message << std::endl;
}

// bar.cpp
void print(std::string message)
{
   std::cout << "[bar]" << message << std::endl;
}

Bunu VC++ compiler ile derleğimizde aşağıdakine benzer bir hata alacaksınız.

foo.obj : error LNK2005: “void __cdecl print(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)” (?print@@YAXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) already defined in bar.obj
cpp_test.exe : fatal error LNK1169: one or more multiply defined symbols found

Aynı isimde iki fonksiyon veya başka tanımlamalar yapmak istediğinizde dosyalardan birindeki tanımı isimsiz namespace içine almanız sorunu çözecektir.

namespace
{
   void print(std::string message)
   {
      std::cout << "[bar]" << message << std::endl;
   }
}

void run()
{
   print("çalışıyor...");
}

Deha fazla detay için şuradaki videoya göz atınız; https://www.youtube.com/watch?v=HrFtpSH-Eso

“L-value” Üçlü koşul dizilimi

Kodları daha kısa ve anlaşılır kılmak için son zamanlarda popüler olan üçlü (ternary) koşul operatörleri C++ içinde de kullanılabiliyor.

Örnek vermek gerekirse;

auto a = 12;
auto b = 42;

auto max = a >= b ? a : b;

Yukarıdaki kod aslında aşağıdaki ile aynı sonucu verir. Yani basit bir ifade ile max değişkenine atanacak değer a değişkeninin b değişkenine eşit veya büyük olmasına bağlı olarak ya a yada b olarak belirlenir.

auto max = a;
if (b > a) max = b;

Bununla birlikte, üç koşullu operatör aynı zamanda, atama işleminin sol tarafındaki bir L-value (L-değeri) olarak da kullanılabilir.

c % 2 == 0 ? a : b = 1;

veya

c % 2 == 0 ? a : b = a >= b ? -a : -b;

L-value nedir detay için tıklayınız (İng.)