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 */ }
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.)