Stil

Stilin en önemli tarafı tutarlılıktır. İkinci en önemli kısım ise ortalama bir C++ programcısının okuyacabileceği bir stil oluşturmaktır.

C++ keyfi olarak seçilen bir uzunlukta tanımlayacı(identifier) ismine izin verir, Bu yüzden birşeyleri isimlendirirken fazla kısa tutmak için bir neden yoktur. Açıklayıcı isimler kullan ve stil konusunda tutarlı ol.

  • CamelCase
  • snake_case

yaygın örnekler. snake_case'in, istenildiği durumda yazım denetleyicisi ile çalışması gibi bir avantajı vardır.

Stil Rehberi Oluşturma

Nasıl bir stil rehberi oluşturursan oluştur, stilini özelleştiren bir .clang-format dosyası implement et. Bu isimlendirmede yardımcı olmaz ama tutarlı bir stile sahip olmak özellikle de açık kaynak projelerin bakımında önemlidir.

Her IDE ve pek çok editör entegre clang-format desteğine sahiptir veya kolayca eklenti olarak yüklenebilir.

Yaygın C++ İsimlendirme Kuralları

  • Tipler büyük harfle başlar: MyClass.
  • Fonksiyonlar ve değişkenler küçük harfle başlar: myMethod.
  • Sabitler tamamen büyük harflidir: const double PI=3.14159265358979323;.

C++ Standard Kütüphanesi (ve Boost gibi iyi bilinen diğer C++ kütüphaneleri) şu kuralları kullanır:

  • Macro isimleri büyük harfledir ve alt çizgi kullanır: INT_MAX.
  • Template parametreleri camel case kullanır: InputIterator.
  • Diğer tüm isimler snake case kullanır: unordered_map.

Private Nesne Verilerini Ayırt Etme

Private veriyi, public veriden ayırt etmek için m_ önekini kullanarak isimlendir. m_, "member"ın kısaltmasıdır.

Fonksiyon Parameterlerini Ayırt Etme

Kod tabanındaki en önemli şey tutarlılıktır; Bu da tutarlılığa yardım edecebilir:

Fonksiyon parametrelerini t_ öneki kullanarak isimlendir. t_, "the" kelimesinin kısaltması olarak düşünülebilir. Burda amaç fonksiyon parametlerini scope'taki diğer değişkenlerden ayırırken tutarlı bir isimlendirme stratejisi sağlamaktır.

Senin organizasyonun diğer önek veya sonekleri kullanabilir. Bu sadece bir örnek. Bu konu biraz tartışmalı, ilgili tartışma için bkz #11.

struct Size
{
  int width;
  int height;

  Size(int t_width, int t_height) : width(t_width), height(t_height) {}
};

// Bu versiyon thread güvenliği açısından anlamlı olabilir,
// ama bazen verileri saklamaya ihtiyacımız vardır, bazen de yoktur.
class PrivateSize
{
  public:
    int width() const { return m_width; }
    int height() const { return m_height; }
    PrivateSize(int t_width, int t_height) : m_width(t_width), m_height(t_height) {}

  private:
    int m_width;
    int m_height;
};

Hiçbirşeyi _ ile Başlayacak Şekilde İsimlendirme

Eğer isimlendirirsen, derleyici ve standard kütüphane implementasyonu için ayrılmış isimlerle çakışma riski ortaya çıkar.

http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier

İyi Bir Örnek

class MyClass
{
public:
  MyClass(int t_data)
    : m_data(t_data)
  {
  }

  int getData() const
  {
    return m_data;
  }

private:
  int m_data;
};

Build'leri Kaynak Dizininin Dışında Yap

Build ile oluşturulan dosyaların kaynak kodun bulunduğu klasöre değil, bir output klasörüne gittiğinden emin ol.

nullptr Kullan

C++11 null pointer'ı tanımlayan özel bir değer olan nullptr'ı tanıttı. Bu, null pointer yerine kullanılan 0 veya NULL'a tercih edilmelidir.

Yorumlar

Yorum blokları /* */ değil, // kullanmalıdır. // kullanmak, debug yapılacakken, bir bloğu yorum haline getirmede daha kolaydır.

// Bu fonksiyon bir şeyler yapıyor
int myFunc()
{
}

Debug sırasında bu fonksiyon bloğunu yorum haline getiriken şunu yaparız:

/*
// this function does something
int myFunc()
{
}
*/

fonksiyon tanımının üstündeki yorum /* */ şeklinde yorum kullansaydı bunu yapamazdık.

Hiçbir Zaman Header Dosyasında using namespace Kullanma

Bu o namespace'teki tüm dosyaların header dosyasına dahil eder. Bu da namespace'i kirletir ve ileride çakışmaya neden olabilir. Bir implementasyon dosyasına using namespace yazmakta ise sorun yoktur.

Include Korumaları

Header dosyaları, dosya adını tam olarak içeren include korumaları bulundurmak zorundadır. Bu aynı dosyanın tekrar tekrar eklenmesini ve diğer projelerdeki header dosyalarıyla çakışmayı önler.

#ifndef MYPROJECT_MYCLASS_HPP
#define MYPROJECT_MYCLASS_HPP

namespace MyProject {
  class MyClass {
  };
}

#endif

Daha yeni bir standard olan #pragma once kullanmayı da düşünebilirsin. Daha kısadır ve amacı daha belirgindir.

Bloklar için {} Kullan

Bunları kullanmamak kodda semantik hatasına neden olabilir.

// Kotu fikir
// Bu derlenir ve isteneni yapar ama ileride düzeltmeye yapıldığı zaman
// yeterli dikkat gösterilmediğinde şaşırtıcı hatalara sebep olabilir.
for (int i = 0; i < 15; ++i)
  std::cout << i << std::endl;

// Kotu fikir
// Bu durumda ise cout döngünün parçası gibi görünse de aslında değil.
int sum = 0;
for (int i = 0; i < 15; ++i)
  ++sum;
  std::cout << i << std::endl;


// Iyi fikir
// Hangi ifadenin döngünün (veya varsa if bloğunun vs.) parçası olduğu aşikar.
int sum = 0;
for (int i = 0; i < 15; ++i) {
  ++sum;
  std::cout << i << std::endl;
}

Satırları Makul Uzunlukta Tut

// Kötü fikir
// takip etmesi zor
if (x && y && myFunctionThatReturnsBool() && caseNumber3 && (15 > 12 || 2 < 3)) {
}

// İyi fikir
// mantıksal gruplandırma, okuması kolay
if (x && y && myFunctionThatReturnsBool()
    && caseNumber3
    && (15 > 12 || 2 < 3)) {
}

Pek çok proje ve kodlama standardında satırların 80 veya 100 karakteri geçmemesini isteyen yumuşak kurallar vardır. Böyle kodları okumak genelde daha kolaydır. Bu ayrıca, bir ekranda fontları küçültmeden yan yana iki ayrı dosya açmayı mümkün kılar.

Lokal Dosyaları Include Etmek için "" Kullan

... <> sistem include'ları için ayrılmıştır.

// Kötü Fikir. Derleyiciye ekstradan -I direktifini kullanmayı gerektirir
// ve bu standardın dışındadır.
#include <string>
#include <includes/MyHeader.hpp>

// Daha Kötü Fikir
// Daha fazla özel -I direktifi kullanmayı gerektirir ve 
// bu kodun paketlenmesini ve dağıtılmasını zorlaştırır.
#include <string>
#include <MyHeader.hpp>


// İyi Fikir
// Ekstradan bir parametre gerektirmez ve kullanıcıya dosyanın
// local bir dosya olduğunu belli eder.
#include <string>
#include "MyHeader.hpp"

Üye Değişkenlere İlk Değer Atama

...yı initializer list kullanarak yap.

POD tipler için, initializer list performansı ile manuel atamanın performansı aynıdır, ama diğer tipler için açık bir performans kazanımı vardır.

// Kötü Fikir
class MyClass
{
public:
  MyClass(int t_value)
  {
    m_value = t_value;
  }

private:
  int m_value;
};

// Kötü Fikir
// Bu atamadan evvel fazladan m_myOtherClass için fazladan bir 
// constructor çağrısı gerektirir.
class MyClass
{
public:
  MyClass(MyOtherClass t_myOtherClass)
  {
    m_myOtherClass = t_myOtherClass;
  }

private:
  MyOtherClass m_myOtherClass;
};

// İyi Fikir
// Burda bir performans kazanımı yok ama kod daha açık.
class MyClass
{
public:
  MyClass(int t_value)
    : m_value(t_value)
  {
  }

private:
  int m_value;
};

// İyi Fikir
// Burda m_myOtherClass için default constructor hiçbir zaman çağrılmaz, bu yüzden
// eğer MyOtherClass is_trivially_default_constructible değilse bir performans kazanımı vardır.
class MyClass
{
public:
  MyClass(MyOtherClass t_myOtherClass)
    : m_myOtherClass(t_myOtherClass)
  {
  }

private:
  MyOtherClass m_myOtherClass;
};

C++11'de üyelere default değerler verebilirsiniz (= veya {} kullanarak).

= ile default değerler atama

// ... //
private:
  int m_value = 0; // allowed
  unsigned m_value_2 = -1; // signed'dan unsigned'a yuvarlamaya izin verilir
// ... //

Bu hiçbir constructor'ın üye nesneye ilk değer atamayı "unutmayacağından" emin olur.

Süslü parantezle kullanarak default değerler atama

Süslü parantezle ilk değer atama, derleme zamanında yuvarlamaya izin vermez.

// En İyi Fikir

// ... //
private:
  int m_value{ 0 }; // allowed
  unsigned m_value_2 { -1 }; // signed'dan unsigned'a yuvarlamaya izin verirmez, derleme zamanı hatasına neden olur
// ... //

Yapmaman için bir neden yoksa {} ile ilk değer atamayı, = kullanmaya tercih et.

Bir üyeye ilk değer atamayı unutmak bulması oldukça zor olan belirsiz davranış buglarının kaynağıdır.

Eğer üye değişkenin ilk değer atamasından sonra değişmesini beklemiyorsan, onu const olarak işaretle.

class MyClass
{
public:
  MyClass(int t_value)
    : m_value{t_value}
  {
  }

private:
  const int m_value{0};
};

Çünkü const üyeye yeni bir değer atanamaz, böyle bir sınıfın anlamlı bir kopya atama operatörü olmayabilir.

Her Zaman Namespace'leri Kullan

Bir tanımlayıcıyı global namespace'te tanımlamak için hemem hemen hiçbir neden yok. Fonksiyonlar ve sınıflar doğru isimlendirilmiş bir namespace içinde durmalı. Global namespace'teki tanımlayıcıların diğer kütüphanelerle çakışma riski bulunur (çoğu C kütüphanesi, C namespace yapısı içermediğinden).

Standard Kütüphane Özellikleri için Doğru Integer Tipini Kullan

Standard Kütüphane uzunlukla alakalı şeyler için genelde std::size_t kullanır. size_t'nin boyu implementasyona göre değişir.

Genelde auto kullanmak pek çok sorunu çözecektir ama her zaman değil.

Doğru integer tipi kullandığından ve C++ standard kütüphanesiyle uyumlu kaldığından emin ol. Mevcut platformunda uyarı üretilmeyebilir, ama platform değiştirdiğinde üretilecektir.

unsigned değerler üzerinde operasyonlar yapmak integer underflow'a neden olabilir, örneğin:

std::vector<int> v1{2,3,4,5,6,7,8,9};
std::vector<int> v2{9,8,7,6,5,4,3,2,1};
const auto s1 = v1.size();
const auto s2 = v2.size();
const auto diff = s1 - s2; // diff underflow sonucu çok büyük bir sayı olacaktır

Dosya Uzantılarında .cpp ve .hpp Kullan

Bu büyük ölçüde tercih meselesi ama .hpp ve .cpp derleyici ve araçlar tarafından iyi kabul görür. Bu yüzden seçim pragmatiktir. Özellikle, Visual Studio .cpp ve .cxx uzantılarını tanır, Vim .cc uzantısını tanımayabilir.

Büyük bir proje (OpenStudio) kullanıcı tarafından üretilen dosyalar için .hpp ve .cpp, araç tarafından geliştirilen dosyalar için .hxx ve .cxx kullandı. Her ikisi de iyi tanınan ve ayırt etmede yardımcı olan uzantılar.

Asla Tab ve Space'leri Karıştırarak Kullanma

Bazı editörler default olarak girintilerin tab ve space karışımı olmasını izin veriyor. Bu aynı girinti ayarlarını kullanmayan başkaları için kodu okunmaz kılar. Bunun olmaması için editörünü ona göre ayarla.

Yan Etkisi Olabilecek Kodları Hiçbir Zaman assert() İçine Koyma

assert(registerSomeThing()); // registerSomeThing() fonksiyonunun true değer döndürdüğünden emin ol

Yukarıdaki kod debug build yaparken başarılı şekilde çalışır ancak release build yaparken derleyici tarafında kaldırılır, bu da debug ve release build'lerde farklı davranışa neden olur. Bu nedenle assert() macrosu release modda hiçbir işe yaramaz.

Template'lerden Korkma

Template'ler DRY prensibini uygulamana yardımcı olur. Template'ler macro'lara tercih edilmelidir, çünkü macrolar, namespace'lere riayet etmez.

Operator Overload'u Mantıklıca Kullan

Operatör overload daha açıklayıcı bir sözdizimi için icat edildi. Açıklayıcı ile demek istediğimiz iki büyük tamsayıyı toplamak, a.add(b) gibi değil a + b gibi görünür. Diğer yaygın bir örnek std::string, iki string'i string1 + string2 kullanarak eklemek epey yaygındır.

Buna rağmen, çok fazla veya yanlış operator overload kullanarak kolayca okuması zor ifadeler oluşturabilirsin. Operator overload ederken, stackoverflow'da açıklanan 3 temel kurala uyulmalı.

Özellikle, bunları aklında bulundurmalısın:

  • Kaynakları işlerken operator=() overload'unu yapmak şart. Aşağıda Sıfır Kuralını Düşün'e bakınız.
  • Diğer tüm operatörleri bulundukları bağlamda yaygın olan şekliyle overload et. Yani tipik senaryolarda eklemek için +, ifadeleri negatif yapmak "true" veya "false" gibi düşünülebilir vesaire.
  • Her zaman operator üstünlüğüne göre hareket et ve sezgiye ters düşecek yapılardan kaçın.
  • Sayısal bir tip implement etmiyorsan veya özel bir alanda sık kullanılan bir sözdizimi değilse, ~ veya % gibi operatörleri overload etme.
  • Asla operator,() (virgül) operatörünü overload etme.
  • Stream'lerle çalışırken üye olmayan opertor>>() ve operator<<() kullan. Mesela, sınıfını stream'e "yazdırmak" için operator<<(std::ostream &, MyClass const &) kullanabilirsin, std::cout veya bir std::fstream veya da std::stringstream. İkincisi sıklıkla bir değerin string şeklini oluşturmada kullanılır.
  • Overload edilebilecek bazı diğer operatörler de var, burda açıklandı.

Kendi özel operatörlerinin implementasyon detaylarıyla ilgili daha fazla ipucu burda bulunabilir.

Implicit Dönüşümleri Engelle

Tek Parametreli Constructor'ler

Tek parametreli constructor'lar derleme zamanında tipler arasında otomatik dönüşümde kullanılabilir. Bu std::string(const char *) gibi şeyler için kullanışlıdır ama genelde kullanılmamalıdır çünkü yanlışlıkla çalışma zamanı fazlalılığı ekleyebilir.

Bunun yerine tek parametreli constructor'ları explicit olarak işaretlemeliyiz böylece belirgin biçimde (explicitly) çağrılmaları gerekir.

Dönüşüm Operatörleri

Tek parametreli constructor'lara benzer şekilde, dönüşüm operatörleri derleyici tarafından çağrılır ve beklenmedik fazlalığına neden olur. Bunlar da explicit olarak işaretlenmelidir.

//kötü fikir
struct S {
  operator int() {
    return 2;
  }
};
//iyi fikir
struct S {
  explicit operator int() {
    return 2;
  }
};

Sıfır Kuralını Düşün

Sıfır Kuralı, eğer sahiplikle uzun uzadıya uğraşmıyorsan derleyicinin sağladığı fonksiyonları (kopyalama constructor'ı, kopyalama atama operatörü, taşıma constructor'ı, taşıma atama operatörü, destructor) yazmamana gerek olmadığını vurgular.

Amaç, derleyicinin daha fazla üye değişkeni eklendiğinde otomatik olarak korunan en uygun sürümleri sağlamasına izin vermektir.

Orijinal makale bir arka plan sağlarken, onu takip eden bu makale neredeyse zamanın %100'ünde bu teknikleri implement etmeyi amaçlar.

results matching ""

    No results matching ""