Kurung kurawal yang tidak perlu di C ++?

187

Ketika melakukan review kode untuk seorang rekan hari ini saya melihat hal yang aneh. Dia telah mengelilingi kode barunya dengan kurung kurawal seperti ini:

Constructor::Constructor()
{
   existing code

   {
      New code: do some new fancy stuff here
   }

   existing code
}

Apa hasilnya, jika ada, dari ini? Apa yang bisa menjadi alasan untuk melakukan ini? Dari mana kebiasaan ini berasal?

Edit:

Berdasarkan input dan beberapa pertanyaan di bawah ini saya merasa bahwa saya harus menambahkan beberapa ke pertanyaan, meskipun saya sudah menandai jawaban.

Lingkungan adalah perangkat yang disematkan. Ada banyak kode warisan C yang dibungkus pakaian C ++. Ada banyak C yang berubah menjadi pengembang C ++.

Tidak ada bagian penting dalam bagian kode ini. Saya hanya melihatnya di bagian kode ini. Tidak ada alokasi memori utama yang dilakukan, hanya beberapa flag yang disetel, dan beberapa twiddling.

Kode yang dikelilingi oleh kurung kurawal adalah sesuatu seperti:

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

(Jangan pedulikan kodenya, cukup tempelkan ke kurung kurawal ...;)) Setelah kurung kurawal ada beberapa yang lebih mengutak-atik, memeriksa keadaan, dan pensinyalan dasar.

Saya berbicara dengan pria itu dan motivasinya adalah untuk membatasi ruang lingkup variabel, penamaan bentrokan, dan beberapa lainnya yang saya tidak bisa mengerti.

Dari POV saya ini tampaknya agak aneh dan saya tidak berpikir bahwa kurung kurawal harus ada dalam kode kita. Saya melihat beberapa contoh bagus di semua jawaban tentang mengapa seseorang bisa mengelilingi kode dengan kurung kurawal, tetapi bukankah seharusnya Anda memisahkan kode menjadi metode?

iikkoo
sumber
99
Apa jawaban kolega Anda ketika Anda bertanya mengapa dia melakukannya?
Graham Borland
20
Cukup umum dengan pola RAII. Ikhtisar singkat: c2.com/cgi/wiki?ResourceAcquisitionIsInisialisasi
Marcin
9
Aku benci kawat gigi keriting yang tidak perlu
jacknad
8
Apakah ada deklarasi di blok dalam?
Keith Thompson
15
mungkin dia hanya ingin dengan mudah 'melipat' bagian baru itu di dalam editornya
wim

Jawaban:

281

Kadang-kadang bagus karena memberi Anda ruang lingkup baru, di mana Anda dapat lebih "bersih" mendeklarasikan variabel baru (otomatis).

Dalam C++hal ini mungkin tidak begitu penting karena Anda dapat memperkenalkan variabel baru di mana saja, tetapi mungkin kebiasaan itu berasal C, di mana Anda tidak dapat melakukan ini sampai C99. :)

Karena C++memiliki destruktor, juga berguna untuk memiliki sumber daya (file, mutex, apa pun) yang secara otomatis dirilis ketika ruang lingkup keluar, yang dapat membuat semuanya lebih bersih. Ini berarti Anda dapat bertahan pada beberapa sumber daya bersama untuk durasi yang lebih pendek daripada yang Anda lakukan jika Anda mengambilnya di awal metode.

beristirahat
sumber
37
1 untuk menyebutkan eksplisit variabel baru dan kebiasaan lama
arne
46
+1 untuk menggunakan ruang lingkup blok yang digunakan untuk membebaskan sumber daya secepat mungkin
Leo
9
Juga mudah untuk 'jika (0)' sebuah blok.
vrdhn
Peninjau kode saya sering memberi tahu saya bahwa saya menulis fungsi / metode terlalu pendek. Untuk membuat mereka senang dan untuk membuat diri saya bahagia (yaitu untuk memisahkan masalah yang tidak terkait, variabel lokalitas dll), saya hanya menggunakan teknik ini seperti yang diuraikan oleh @unwind.
ossandcad
21
@ossandcad, mereka memberi tahu Anda metode Anda "terlalu pendek"? Itu sangat sulit dilakukan. 90% pengembang (termasuk saya sendiri) memiliki masalah sebaliknya.
Ben Lee
169

Salah satu tujuan yang mungkin adalah untuk mengontrol ruang lingkup variabel . Dan karena variabel dengan penyimpanan otomatis dihancurkan ketika mereka keluar dari ruang lingkup, ini juga dapat memungkinkan destruktor disebut lebih awal daripada yang seharusnya.

ruakh
sumber
14
Tentu saja, blok itu seharusnya dibuat menjadi fungsi terpisah.
BlueRaja - Danny Pflughoeft
8
Catatan sejarah: Ini adalah teknik dari bahasa C awal yang memungkinkan pembuatan variabel temporer lokal.
Thomas Matthews
12
Saya harus mengatakan - walaupun saya puas dengan jawaban saya, itu sebenarnya bukan jawaban terbaik di sini; jawaban yang lebih baik menyebutkan RAII secara eksplisit, karena itu adalah alasan utama mengapa Anda ingin destructor dipanggil pada titik tertentu. Ini seperti kasus "senjata tercepat di Barat": Saya memasang cukup cepat sehingga saya mendapat cukup banyak upvotes awal sehingga saya memperoleh "momentum" untuk mendapatkan upvotes lebih cepat daripada beberapa jawaban yang lebih baik. Bukannya aku mengeluh! :-)
ruakh
7
@ BlueRaja-DannyPflughoeft Kau terlalu menyederhanakan. "Letakkan di fungsi yang terpisah" bukan solusi untuk setiap masalah kode. Kode di salah satu blok ini mungkin sangat erat dengan kode di sekitarnya, menyentuh beberapa variabelnya. Menggunakan fungsi C, yang membutuhkan operasi pointer. Selain itu, tidak setiap cuplikan kode dapat (atau seharusnya) dapat digunakan kembali, dan kadang-kadang kode tersebut mungkin tidak masuk akal sendiri. Saya kadang-kadang menempatkan blok di sekitar forpernyataan saya untuk membuat berumur pendek int i;di C89. Tentunya Anda tidak menyarankan bahwa setiap orang forharus berada dalam fungsi yang terpisah?
Anders Sjöqvist
101

Kawat gigi tambahan digunakan untuk menentukan ruang lingkup variabel yang dideklarasikan di dalam kawat gigi. Hal ini dilakukan agar destruktor akan dipanggil ketika variabel keluar dari ruang lingkup. Di destruktor, Anda dapat merilis mutex (atau sumber daya lainnya) sehingga orang lain bisa mendapatkannya.

Dalam kode produksi saya, saya telah menulis sesuatu seperti ini:

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
}

Seperti yang Anda lihat, dengan cara ini, Anda dapat menggunakan scoped_lock dalam suatu fungsi dan pada saat yang sama, dapat menentukan cakupannya dengan menggunakan kawat gigi tambahan. Ini memastikan bahwa meskipun kode di luar kawat gigi tambahan dapat dieksekusi oleh beberapa utas secara bersamaan, kode di dalam kawat gigi akan dieksekusi dengan tepat satu utas pada satu waktu.

Nawaz
sumber
1
Saya pikir lebih bersih hanya dengan memiliki: scoped_lock lock (mutex) // kode bagian kritis lalu lock.unlock ().
mendesis
17
@szielenski: Bagaimana jika kode dari bagian kritis melempar pengecualian? Entah mutex akan terkunci selamanya, atau kodenya tidak akan sebersih yang Anda katakan.
Nawaz
4
@Nawaz: Pendekatan @ szielenski tidak akan membiarkan mutex terkunci dalam kasus pengecualian. Dia juga menggunakan a scoped_lockyang akan dihancurkan pada pengecualian. Saya biasanya lebih suka memperkenalkan ruang lingkup baru untuk kunci juga, tetapi dalam beberapa kasus unlockini sangat berguna. Misalnya untuk mendeklarasikan variabel lokal baru di dalam bagian kritis dan kemudian menggunakannya nanti. (Saya tahu saya terlambat, tetapi hanya untuk kelengkapan ...)
Stephan
51

Seperti yang telah ditunjukkan orang lain, blok baru memperkenalkan ruang lingkup baru, memungkinkan seseorang untuk menulis sedikit kode dengan variabel sendiri yang tidak membuang namespace dari kode sekitarnya, dan tidak menggunakan sumber daya lebih lama dari yang diperlukan.

Namun, ada alasan bagus lain untuk melakukan ini.

Ini hanya untuk mengisolasi blok kode yang mencapai tujuan (sub) tertentu. Jarang pernyataan tunggal mencapai efek komputasi yang saya inginkan; biasanya dibutuhkan beberapa. Menempatkan mereka di blok (dengan komentar) memungkinkan saya memberi tahu pembaca (seringkali saya sendiri di kemudian hari):

  • Potongan ini memiliki tujuan konseptual yang koheren
  • Inilah semua kode yang dibutuhkan
  • Dan inilah komentar tentang bongkahan itu.

misalnya

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

Anda mungkin berpendapat saya harus menulis fungsi untuk melakukan semua itu. Jika saya hanya melakukannya sekali, menulis fungsi hanya menambah sintaks dan parameter tambahan; sepertinya tidak ada gunanya. Anggap saja ini sebagai fungsi anonim tanpa parameter.

Jika Anda beruntung, editor Anda akan memiliki fungsi lipatan / buka yang bahkan akan membiarkan Anda menyembunyikan blokir.

Saya melakukan ini sepanjang waktu. Sangat menyenangkan mengetahui batas-batas kode yang perlu saya periksa, dan bahkan lebih baik untuk mengetahui bahwa jika potongan itu bukan yang saya inginkan, saya tidak harus melihat salah satu baris.

Ira Baxter
sumber
23

Salah satu alasannya adalah bahwa masa pakai variabel yang dideklarasikan di dalam blok kurung kurawal baru terbatas pada blok ini. Alasan lain yang terlintas dalam pikiran adalah untuk dapat menggunakan kode lipat di editor favorit.

arne
sumber
17

Ini sama dengan blok if(atau whiledll.), Hanya tanpa if . Dengan kata lain, Anda memperkenalkan ruang lingkup tanpa memperkenalkan struktur kontrol.

"Pelingkupan eksplisit" ini biasanya berguna dalam kasus-kasus berikut:

  1. Untuk menghindari bentrokan nama.
  2. Untuk ruang lingkup using.
  3. Untuk mengontrol kapan destruktor dipanggil.

Contoh 1:

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

// ...

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

Jika my_variableterjadi menjadi sangat baik nama untuk dua variabel yang berbeda yang digunakan dalam isolasi dari satu sama lain, maka scoping eksplisit memungkinkan Anda untuk menghindari menciptakan nama baru hanya untuk menghindari nama bentrokan.

Ini juga memungkinkan Anda untuk menghindari penggunaan di my_variableluar ruang lingkup yang dimaksudkan secara tidak sengaja.

Contoh 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.
        // ...
    }

}

Situasi praktis saat ini bermanfaat jarang terjadi dan dapat mengindikasikan bahwa kode tersebut sudah matang untuk refactoring, tetapi mekanismenya ada jika Anda benar-benar membutuhkannya.

Contoh 3:

{
    MyRaiiClass guard1 = ...;

    // ...

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

    // ...

} // ~MyRaiiClass for guard1 called.

Ini bisa penting untuk RAII dalam kasus ketika kebutuhan untuk membebaskan sumber daya tidak secara alami "jatuh" ke batas fungsi atau struktur kontrol.

Branko Dimitrijevic
sumber
15

Ini sangat berguna ketika menggunakan kunci scoped bersama dengan bagian-bagian penting dalam pemrograman multithreaded. Kunci scoped Anda diinisialisasi dalam kurung kurawal (biasanya perintah pertama) akan keluar dari ruang lingkup pada akhir akhir blok dan agar utas lainnya dapat berjalan kembali.

belajar
sumber
14

Semua orang sudah membahas dengan benar kemungkinan pelingkupan, RAII dll., Tetapi karena Anda menyebutkan lingkungan yang disematkan, ada satu alasan potensial lebih lanjut:

Mungkin pengembang tidak mempercayai alokasi register kompiler ini atau ingin secara eksplisit mengontrol ukuran frame stack dengan membatasi jumlah variabel otomatis dalam cakupan sekaligus.

Di sini isInitkemungkinan akan ada di tumpukan:

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

Jika Anda mengeluarkan kurung kurawal, ruang untuk isInitdapat dicadangkan dalam bingkai tumpukan bahkan setelah berpotensi digunakan kembali: jika ada banyak variabel otomatis dengan cakupan yang sama lokalnya, dan ukuran tumpukan Anda terbatas, itu bisa menjadi masalah.

Demikian pula, jika variabel Anda dialokasikan untuk register, keluar dari ruang lingkup harus memberikan petunjuk kuat bahwa register sekarang tersedia untuk digunakan kembali. Anda harus melihat assembler yang dihasilkan dengan dan tanpa kawat gigi untuk mencari tahu apakah ini membuat perbedaan nyata (dan profil itu - atau perhatikan stack overflow - untuk melihat apakah perbedaan ini benar-benar penting).

Tak berguna
sumber
+1 poin bagus, meskipun saya cukup yakin kompiler modern mendapatkan hak ini tanpa intervensi. (IIRC - setidaknya untuk kompiler non-embedded - mereka mengabaikan kata kunci 'register' sejauh '99 karena mereka selalu dapat melakukan pekerjaan yang lebih baik daripada yang Anda bisa.)
Rup
11

Saya pikir orang lain sudah membahas pelingkupan, jadi saya akan menyebutkan kawat gigi yang tidak perlu mungkin juga melayani tujuan dalam proses pengembangan. Misalnya, Anda mengerjakan optimasi untuk fungsi yang sudah ada. Mengalihkan optimisasi atau melacak bug ke urutan pernyataan tertentu mudah bagi programmer - lihat komentar sebelum kawat gigi:

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

Praktik ini berguna dalam konteks tertentu seperti debugging, perangkat tertanam, atau kode pribadi.

kertronux
sumber
10

Saya setuju dengan "ruakh". Jika Anda ingin penjelasan yang baik tentang berbagai tingkat cakupan dalam C, lihat pos ini:

Berbagai Tingkat Lingkup dalam Aplikasi C

Secara umum, penggunaan "Ruang lingkup blok" sangat membantu jika Anda hanya ingin menggunakan variabel sementara yang Anda tidak harus melacak selama panggilan fungsi berlangsung. Selain itu, beberapa orang menggunakannya sehingga Anda dapat menggunakan nama variabel yang sama di beberapa lokasi untuk kenyamanan, meskipun itu umumnya bukan ide yang baik. misalnya:

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;
}

Dalam contoh khusus ini, saya telah mendefinisikan returnValue dua kali, tetapi karena itu hanya pada ruang lingkup blok, alih-alih ruang lingkup fungsi (yaitu: ruang lingkup fungsi akan, misalnya, menyatakan returnValue tepat setelah int main (void)), saya tidak dapatkan kesalahan kompilator, karena setiap blok tidak menyadari contoh sementara dari returnValue yang dideklarasikan.

Saya tidak bisa mengatakan bahwa ini adalah ide yang baik secara umum (yaitu: Anda mungkin tidak harus menggunakan kembali nama variabel berulang kali dari blok-ke-blok), tetapi secara umum, ini menghemat waktu dan memungkinkan Anda menghindari keharusan mengelola nilai returnValue di seluruh fungsi.

Terakhir, harap perhatikan ruang lingkup variabel yang digunakan dalam sampel kode saya:

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
Awan
sumber
Pertanyaan yang sibuk, bung. Saya tidak pernah memiliki 100 up. Apa yang istimewa dari pertanyaan ini? Tautan yang bagus. C lebih berharga daripada C ++.
Wolfpack'08
5

Jadi, mengapa menggunakan kurung kurawal yang tidak perlu?

  • Untuk tujuan "Pelingkupan" (sebagaimana disebutkan di atas)
  • Membuat kode lebih mudah dibaca (seperti menggunakan #pragma, atau mendefinisikan "bagian" yang dapat divisualisasikan)
  • Karena kamu bisa. Sederhana seperti itu.

PS Ini bukan kode BAD; 100% valid. Jadi, ini lebih merupakan masalah rasa (tidak biasa).

Dr.Kameleon
sumber
5

Setelah melihat kode di edit, saya dapat mengatakan bahwa tanda kurung yang tidak perlu mungkin (dalam tampilan coders asli) menjadi 100% jelas apa yang akan terjadi selama if / then, bahkan jika itu hanya satu baris sekarang, mungkin saja lebih banyak baris kemudian, dan kurung menjamin Anda tidak akan membuat kesalahan.

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

jika di atas adalah asli, dan menghapus "ekstra" akan menghasilkan:

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

lalu, modifikasi selanjutnya mungkin terlihat seperti ini:

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

dan itu, tentu saja, akan menimbulkan masalah, karena sekarang isInit akan selalu dikembalikan, terlepas dari jika / kemudian.

nycynik
sumber
4

Objek dihancurkan secara otomatis ketika mereka keluar dari ruang lingkup ...


sumber
2

Contoh lain penggunaan adalah kelas terkait UI, terutama Qt.

Misalnya, Anda memiliki beberapa UI yang rumit dan banyak widget, masing-masing memiliki spasi, tata letak, dll. Daripada menamai mereka, space1, space2, spaceBetween, layout1, ...Anda dapat menyelamatkan diri dari nama non-deskriptif untuk variabel yang hanya ada dalam dua-tiga baris kode.

Yah, beberapa mungkin mengatakan bahwa Anda harus membaginya dalam metode, tetapi membuat 40 metode yang tidak dapat digunakan kembali tidak terlihat ok - jadi saya memutuskan untuk hanya menambahkan kawat gigi dan komentar di depan mereka, jadi sepertinya blok logis. Contoh:

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

Tidak bisa mengatakan itu praktik terbaik, tapi bagus untuk kode lama.

Mendapat masalah ini ketika banyak orang menambahkan komponen mereka sendiri ke UI dan beberapa metode menjadi sangat besar, tetapi tidak praktis untuk membuat 40 metode sekali pakai di dalam kelas yang sudah kacau.

htzfun
sumber