C ++ 'da gereksiz kıvırcık parantez?


188

Bugün bir meslektaşım için kod incelemesi yaparken tuhaf bir şey gördüm. Yeni kodunu şöyle kıvırcık ayraçlarla kuşatmıştı:

Constructor::Constructor()
{
   existing code

   {
      New code: do some new fancy stuff here
   }

   existing code
}

Bundan varsa, sonuç nedir? Bunu yapmanın nedeni ne olabilir? Bu alışkanlık nereden geliyor?

Düzenle:

Girdi ve aşağıdaki bazı sorulara dayanarak, zaten bir cevap işaretlememe rağmen soruya biraz eklemem gerektiğini hissediyorum.

Ortam, yerleşik aygıtlardır. C ++ giysilerinde sarılmış çok eski C kodu var. C döndü C ++ geliştiricileri bir yeri vardır.

Kodun bu bölümünde kritik bölüm yoktur. Ben sadece kodun bu bölümünde gördüm. Büyük bellek ayırma işlemleri yapılmaz, sadece ayarlanmış bazı bayraklar ve biraz döndürme yapılır.

Kıvırcık parantez ile çevrili kod şuna benzer:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
}

(Kodu önemsemeyin, sadece kıvırcık parantezlere sadık kalın ...;)) Kıvırcık parantezlerden sonra biraz daha twiddling, durum kontrolü ve temel sinyalleme vardır.

Adamla konuştum ve motivasyonu değişkenlerin kapsamını, isimlendirme çatışmalarını ve gerçekten alamadığım diğerlerini sınırlamaktı.

POV'umdan bu oldukça garip görünüyor ve kıvırcık parantezlerin kodumuzda olması gerektiğini düşünmüyorum. Birinin kodu neden kaşlı ayraçlarla kuşatabileceğine dair tüm cevaplarda iyi örnekler gördüm, ancak kodu bunun yerine yöntemlere ayırmamalısınız?


99
Neden yaptığını sorduğunuzda meslektaşınızın cevabı neydi?
Graham Borland

20
RAII modelinde oldukça yaygındır. Hızlı gözden geçirme
Marcin

9
Gereksiz kıvırcık parantezlerden nefret ediyorum
jacknad

8
İç blokta herhangi bir açıklama var mıydı?
Keith Thompson

15
belki de editöründeki yeni bölümü kolayca 'katlamak' istedi
wim

Yanıtlar:


282

Bazen yeni (otomatik) yeni (otomatik) değişkenler tanımlayabileceğiniz yeni bir kapsam sağladığı için bazen güzeldir.

Gelen C++her yerde yeni değişkenler tanıtmak olabileceğinden bu belki çok önemli değil, ama belki de alışkanlık dan Csen C99 kadar bu yapamadı nerede. :)

Yana C++yıkıcı vardır, o da otomatik olarak işler süpürge yapabilir kapsam çıkışları olarak yayımlanan kaynaklar (ne olursa olsun dosyaları, muteksleri,) olması kullanışlı olabilir. Bu, bazı paylaşılan kaynaklara yöntemin başlangıcında yakaladığınız süreden daha kısa süre dayanabileceğiniz anlamına gelir.


37
Yeni değişkenlerin ve eski alışkanlıkların açıkça belirtilmesi için +1
arne

46
Kaynakları olabildiğince hızlı serbest bırakmak için kullanılan blok kapsamını kullanmak için +1
Leo

9
Ayrıca, bir bloğu '(0)' yapmak da kolaydır.
vrdhn

Kod gözden geçirenlerim genellikle çok kısa işlevler / yöntemler yazdığımı söylerler. Onları mutlu etmek ve kendimi mutlu etmek için (yani ilgisiz endişeleri, değişken yerellik vb. Ayırmak için), @unwind tarafından ayrıntılı olarak anlatılan bu tekniği kullanıyorum.
ossandcad

21
@ossandcad, yöntemlerinizin "çok kısa" olduğunu söylüyorlar mı? Bunu yapmak son derece zor. Geliştiricilerin% 90'ında (büyük olasılıkla dahil) ters problem var.
Ben Lee

169

Olası bir amaç, değişken kapsamı kontrol etmektir . Ve otomatik depolamalı değişkenler kapsam dışına çıktıklarında yok edildiğinden, bu, bir yıkıcıya aksi belirtilmedikçe daha önce çağrılabilir.


14
Tabii ki, gerçekten bu blok sadece ayrı bir fonksiyona dönüştürülmelidir.
BlueRaja - Danny Pflughoeft

8
Tarihsel not: Bu, erken C dilinden yerel geçici değişkenlerin oluşturulmasına izin veren bir tekniktir.
Thomas Matthews

12
Söylemeliyim - cevabımdan memnun olmama rağmen, bu gerçekten en iyi cevap değil; daha iyi yanıtlar RAII'den açıkça bahseder, çünkü bir yıkıcının belirli bir noktada çağrılmasını istemenizin ana nedeni budur . Bu, "Batı'daki en hızlı silah" örneği gibi görünüyor: Daha iyi cevaplardan daha hızlı oy almak için "momentum" kazandığım kadar yeterli erken upvotes aldım. Şikayet ettiğimden değil! :-)
ruakh

7
@ BlueRaja-DannyPflughoeft Çok basitsiniz. "Ayrı bir işleve koy" her kod sorununun çözümü değildir. Bu bloklardan birindeki kod, değişkenlerinin birkaçına dokunarak çevredeki kodla sıkıca birleştirilebilir. İşaretçi işlemleri gerektiren C işlevlerini kullanma. Ayrıca, her kod snippet'i tekrar kullanılamaz (veya olması gerekir) ve bazen kod kendi başına anlamlı olmayabilir. Bazen C89'da forkısa ömürlü oluşturmak için ifadelerimin etrafına bloklar koydum int i;. Elbette her şeyin forayrı bir işlevde olmasını önermiyor musunuz?
Anders Sjöqvist

101

Ekstra ayraçlar, ayraçlar içinde bildirilen değişkenin kapsamını tanımlamak için kullanılır. Değişken kapsam dışına çıktığında yıkıcı çağrılır. Yıkıcıda, bir muteksi (veya başka bir kaynağı) serbest bırakabilirsiniz, böylece diğeri bunu elde edebilir.

Üretim kodumda şöyle bir şey yazdım:

void f()
{
   //some code - MULTIPLE threads can execute this code at the same time

   {
       scoped_lock lock(mutex); //critical section starts here

       //critical section code
       //EXACTLY ONE thread can execute this code at a time

   } //mutex is automatically released here

  //other code  - MULTIPLE threads can execute this code at the same time
}

Gördüğünüz gibi, bu şekilde scoped_lock bir işlevde kullanabilirsiniz ve aynı zamanda ekstra parantez kullanarak kapsamını tanımlayabilirsiniz. Bu, ek parantezlerin dışındaki kodun aynı anda birden çok iş parçacığı tarafından yürütülmesine rağmen, parantez içindeki kodun bir seferde tam olarak bir iş parçacığı tarafından yürütülmesini sağlar .


1
Ben sadece sahip olmak daha temiz olduğunu düşünüyorum: scoped_lock lock (mutex) // kritik bölüm kodu sonra lock.unlock ().
cızırtı

17
@szielenski: Kritik bölümdeki kod istisna atarsa ​​ne olur? Muteks sonsuza kadar kilitlenir ya da kod dediğin gibi o kadar temiz olmaz .
Nawaz

4
@Nawaz: @ szielenski'nin yaklaşımı istisnalar durumunda muteksi kilitli bırakmayacak. Ayrıca scoped_lockistisnalar nedeniyle tahrip edilecek olanı kullanır . Genellikle kilit için de yeni bir kapsam sunmayı tercih ederim, ancak bazı durumlarda unlockçok yararlıdır. Örneğin, kritik bölüm içinde yeni bir yerel değişken bildirmek ve daha sonra kullanmak. (Geç olduğumu biliyorum, ama sadece bütünlük için ...)
Stephan

51

Diğerlerinin de işaret ettiği gibi, yeni bir blok yeni bir kapsam tanıtarak, kişinin kendi değişkenleriyle çevreleyen kodun ad alanını çöpe atmayan ve kaynakları gereğinden fazla kullanmayan bir kod yazmasına olanak tanır.

Ancak, bunu yapmanın başka bir nedeni daha var.

Sadece belirli bir (alt) amaca ulaşan bir kod bloğunu izole etmektir. Tek bir ifadenin istediğim bir hesaplama etkisi yaratması nadirdir; genellikle birkaç tane alır. Bunları bir bloğa yerleştirmek (yorum ile) okuyucuya (genellikle kendimi daha sonraki bir tarihte) söylememe izin verir:

  • Bu parça tutarlı bir kavramsal amaca sahiptir
  • İşte gerekli tüm kod
  • Ve işte yığın hakkında bir yorum.

Örneğin

{  // update the moving average
   i= (i+1) mod ARRAYSIZE;
   sum = sum - A[i];
   A[i] = new_value;
   sum = sum + new_value;
   average = sum / ARRAYSIZE ;  
}

Tüm bunları yapmak için bir işlev yazmam gerektiğini iddia edebilirsiniz. Sadece bir kez yaparsam, bir işlev yazmak sadece ek sözdizimi ve parametreler ekler; çok az anlamı var. Bunu parametresiz, anonim bir işlev olarak düşünün.

Eğer şanslıysanız, editörünüzde bloğu gizlemenize bile izin verecek bir katlama / açma fonksiyonu olacaktır.

Bunu her zaman yaparım. İncelemem gereken kodun sınırlarını bilmek büyük bir zevk ve bu yığın istediğim değilse, satırlardan herhangi birine bakmak zorunda olmadığımı bilmek daha da iyi.


23

Bunun bir nedeni, yeni süslü parantez bloğunda bildirilen değişkenlerin kullanım ömrünün bu blokla sınırlı olması olabilir. Akla gelen başka bir neden, en sevdiğiniz editörde kod katlamayı kullanabilmektir.


17

Bu , sadece if(veya whilevb.) Bir blokla aynıdır , sadece olmadan if . Başka bir deyişle, bir kontrol yapısı sunmadan bir kapsamı tanıtırsınız.

Bu "açık kapsam belirleme" genellikle aşağıdaki durumlarda kullanışlıdır:

  1. İsim çatışmalarından kaçınmak için.
  2. Kapsam için using.
  3. Yıkıcıların ne zaman çağrıldığını kontrol etmek için.

Örnek 1:

{
    auto my_variable = ... ;
    // ...
}

// ...

{
    auto my_variable = ... ;
    // ...
}

Eğer my_variableözellikle iyi olur adı birbirlerinden ayrı olarak kullanılan iki farklı değişkenler için, daha sonra açık kapsam sadece adı çatışması önlemek için yeni bir isim icat önlemek için izin verir.

Bu ayrıca my_variableamaçlanan kapsamını yanlışlıkla kullanmamanızı sağlar.

Örnek 2:

namespace N1 { class A { }; }
namespace N2 { class A { }; }

void foo() {

    {
        using namespace N1;
        A a; // N1::A.
        // ...
    }

    {
        using namespace N2;
        A a; // N2::A.
        // ...
    }

}

Bunun yararlı olduğu pratik durumlar nadirdir ve kodun yeniden düzenleme için uygun olduğunu gösterebilir, ancak mekanizma gerçekten ihtiyacınız olduğunda oradadır.

Örnek 3:

{
    MyRaiiClass guard1 = ...;

    // ...

    {
        MyRaiiClass guard2 = ...;
        // ...
    } // ~MyRaiiClass for guard2 called.

    // ...

} // ~MyRaiiClass for guard1 called.

Bu, kaynakların serbest bırakılması ihtiyacının doğal olarak fonksiyonların veya kontrol yapılarının sınırlarına "düşmediği" durumlarda RAII için önemli olabilir .


15

Kapsamlı kilitleri çok iş parçacıklı programlamada kritik bölümlerle birlikte kullanırken gerçekten yararlıdır. Kıvırcık parantez içinde başlatılan kapsamlı kilidiniz (genellikle ilk komut) bloğun sonunun sonunda kapsam dışına çıkar ve böylece diğer dişler tekrar çalışabilir.


14

Herkes, kapsam belirleme, RAII vb. Olasılıkları doğru bir şekilde kapsamıştır, ancak gömülü bir ortamdan bahsettiğiniz için, başka bir potansiyel neden daha vardır:

Belki de geliştirici bu derleyicinin kayıt ayırmasına güvenmiyor veya kapsamdaki otomatik değişkenlerin sayısını bir kerede sınırlayarak yığın çerçeve boyutunu açıkça denetlemek istiyor.

İşte isInitmuhtemel yığın olacaktır:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
}

Kıvırcık parantezleri isInitçıkarırsanız, potansiyel olarak yeniden kullanılabildikten sonra bile yığın karesinde alan ayrılabilir: benzer yerelleştirilmiş kapsamı olan çok sayıda otomatik değişken varsa ve yığın boyutunuz sınırlıysa, bu bir sorun olabilir.

Benzer şekilde, değişkeniniz bir sicile tahsis edilmişse, kapsam dışı kalmak sicilin artık yeniden kullanılabilir olduğuna dair güçlü bir ipucu sağlamalıdır. Bunun gerçek bir fark yaratıp yaratmadığını anlamak için parantez ile veya parantez olmadan oluşturulmuş montajcıya bakmanız (ve bu farkın gerçekten önemli olup olmadığını görmek için profilin - veya yığın taşmasına dikkat etmenin) gerekir.


+ 1 iyi nokta, ancak modern derleyicilerin müdahale etmeden bu hakkı elde ettiğinden oldukça eminim. (IIRC - en azından gömülü olmayan derleyiciler için - 'register' anahtar kelimesini '99'a kadar görmezden geldiler çünkü her zaman yapabileceğinizden daha iyi bir iş yapabilirler.)
Rup

11

Sanırım başkaları da kapsam belirlemeyi ele aldı, bu yüzden gereksiz parantezlerin geliştirme sürecinde de amaca hizmet edebileceğinden bahsedeceğim. Örneğin, mevcut bir işleve yönelik bir optimizasyon üzerinde çalıştığınızı varsayalım. Optimizasyonu değiştirmek veya bir hatayı belirli bir ifade dizisine izlemek programcı için basittir - parantez öncesi açıklamaya bakın:

// if (false) or if (0) 
{
   //experimental optimization  
}

Bu uygulama, hata ayıklama, katıştırılmış cihazlar veya kişisel kod gibi bazı bağlamlarda kullanışlıdır.


10

"Ruah" a katılıyorum. C'deki çeşitli kapsam düzeylerinin iyi bir açıklamasını istiyorsanız, bu gönderiye göz atın:

C Uygulamasında Çeşitli Kapsam Seviyeleri

Genel olarak, işlev çağrısının ömrü boyunca izlemeniz gerekmeyen geçici bir değişken kullanmak istiyorsanız "Blok kapsamı" kullanımı yararlıdır. Buna ek olarak, bazı insanlar bunu kullanır, böylece kolaylık sağlamak için aynı değişken adını birden fazla yerde kullanabilirsiniz, ancak bu genellikle iyi bir fikir değildir. Örneğin:

int unusedInt = 1;

int main(void) {
  int k;

  for(k = 0; k<10; k++) {
    int returnValue = myFunction(k);
    printf("returnValue (int) is: %d (k=%d)",returnValue,k);
  }

  for(k = 0; k<100; k++) {
    char returnValue = myCharacterFunction(k);
    printf("returnValue (char) is: %c  (k=%d)",returnValue,k);
  }

  return 0;
}

Bu özel örnekte, returnValue'u iki kez tanımladım, ancak işlev kapsamı yerine sadece blok kapsamında olduğu için (örneğin: işlev kapsamı, örneğin, main main (void) işleminden hemen sonra returnValue'yu bildirmek) her blok bildirilen geçici returnValue örneğinden habersiz olduğu için derleyici hataları alın.

Bunun genel olarak iyi bir fikir olduğunu söyleyemem (yani: muhtemelen değişken adlarını bloktan bloğa tekrar tekrar kullanmamalısınız), ancak genel olarak, zaman kazandırır ve Tüm işlev boyunca returnValue değeri.

Son olarak, lütfen kod örneğimde kullanılan değişkenlerin kapsamına dikkat edin:

int:  unusedInt:   File and global scope (if this were a static int, it would only be file scope)
int:  k:           Function scope
int:  returnValue: Block scope
char: returnValue: Block scope

Meşgul soru dostum. Daha önce hiç 100 up almadım. Bu soru hakkında bu kadar özel olan ne? İyi bağlantı. C, C ++ 'dan daha değerlidir.
Wolfpack'08

5

Peki, neden "gereksiz" kıvırcık parantez kullanalım?

  • "Kapsam Belirleme" amaçları için (yukarıda belirtildiği gibi)
  • Kodu bir şekilde daha okunabilir hale getirmek ( #pragmagörselleştirilebilen "bölümleri" kullanmak veya tanımlamak gibi )
  • Çünkü yapabilirsin. Bu kadar basit.

PS: KÖTÜ kod değil; % 100 geçerlidir. Yani, oldukça (nadir) bir tat meselesi.


5

Düzenlemedeki kodu görüntüledikten sonra, gereksiz parantezlerin (orijinal kodlayıcılar görünümünde) if / then sırasında ne olacağını% 100 net olduğunu söyleyebilirim, şimdi tek bir satır olsa bile, daha sonra daha fazla satır ve parantezler hata yapmayacağınızı garanti eder.

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
   return -1;
}

yukarıdaki orijinal ise ve "ekstralar" woudl kaldırılması ile sonuçlanır:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) 
     return isInit;
   return -1;
}

daha sonra, daha sonraki bir değişiklik şöyle görünebilir:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) 
     CallSomethingNewHere();
     return isInit;
   return -1;
}

ve bu, elbette, bir soruna neden olur, çünkü isInit, if / then ne olursa olsun, her zaman iade edilir.


4

Nesneler kapsam dışına çıktığında otomatik olarak yok edilir ...


2

Diğer bir kullanım örneği, kullanıcı arayüzü ile ilgili sınıflardır, özellikle de Qt.

Örneğin, bazı karmaşık kullanıcı arayüzünüz ve çok sayıda widget'ınız var, her birinin kendi aralığı, düzeni vb. space1, space2, spaceBetween, layout1, ...Var. kodu.

Peki, bazıları yöntemlere bölmeniz gerektiğini söyleyebilir, ancak 40 yeniden kullanılamayan yöntem oluşturmak iyi görünmüyor - bu yüzden onlardan önce parantez ve yorumlar eklemeye karar verdim, bu yüzden mantıksal blok gibi görünüyor. Misal:

// Start video button 
{ 
   <here the code goes> 
}
// Stop video button
{
   <...>
}
// Status label
{
   <...>
}

Bunun en iyi uygulama olduğunu söyleyemem, ancak eski kod için iyi bir uygulama.

Bir çok insan UI'ye kendi bileşenlerini eklediğinde ve bazı yöntemler gerçekten büyük hale geldiğinde bu problemleri yaşadı, ancak sınıfta zaten dağınık olan 40 bir kerelik kullanım yöntemi oluşturmak pratik değil.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.