Mengapa saya dapat mendefinisikan struktur dan kelas dalam suatu fungsi di C ++?

90

Saya hanya salah melakukan sesuatu seperti ini di C ++, dan berhasil. Mengapa saya bisa melakukan ini?

int main(int argc, char** argv) {
    struct MyStruct
    {
      int somevalue;
    };

    MyStruct s;
    s.somevalue = 5;
}

Sekarang setelah melakukan ini, saya ingat pernah membaca tentang trik ini di suatu tempat, dahulu kala, sebagai semacam alat pemrograman fungsional orang miskin untuk C ++, tetapi saya tidak dapat mengingat mengapa ini valid, atau di mana saya membacanya.

Kami menerima jawaban untuk kedua pertanyaan tersebut!

Catatan: Meskipun ketika menulis pertanyaan saya tidak mendapatkan referensi apa pun untuk pertanyaan ini , bilah samping saat ini menunjukkannya jadi saya akan meletakkannya di sini untuk referensi, bagaimanapun pertanyaannya berbeda tetapi mungkin berguna.

Robert Gould
sumber

Jawaban:

70

[EDIT 18/4/2013]: Untungnya, batasan yang disebutkan di bawah ini telah dicabut di C ++ 11, jadi kelas yang ditentukan secara lokal berguna! Terima kasih kepada pemberi komentar bamboon.

Kemampuan untuk mendefinisikan kelas secara lokal akan membuat pembuatan operator()()fungsi kustom (kelas dengan , misalnya fungsi perbandingan untuk meneruskan ke std::sort()atau "badan loop" yang akan digunakan std::for_each()) jauh lebih nyaman.

Sayangnya, C ++ melarang penggunaan kelas yang ditentukan secara lokal dengan template , karena tidak memiliki keterkaitan. Karena sebagian besar aplikasi dari functor melibatkan jenis template yang memiliki template pada jenis functor, kelas yang ditentukan secara lokal tidak dapat digunakan untuk ini - Anda harus menentukannya di luar fungsi. :(

[EDIT 1/11/2009]

Kutipan relevan dari standar adalah:

14.3.1 / 2:. Tipe lokal, tipe tanpa hubungan, tipe tanpa nama atau tipe gabungan dari tipe-tipe ini tidak boleh digunakan sebagai argumen templat untuk parameter tipe templat.

j_random_hacker
sumber
2
Meskipun secara empiris, ini tampaknya bekerja dengan MSVC ++ 8. (Tapi tidak dengan g ++.)
j_random_hacker
Saya menggunakan gcc 4.3.3, dan sepertinya berfungsi di sana: pastebin.com/f65b876b . Apakah Anda memiliki referensi di mana standar melarangnya? Menurut saya, ini dapat dengan mudah dibuat pada saat digunakan.
Catskul
@Catskul: 14.3.1 / 2: "Tipe lokal, tipe tanpa tautan, tipe tanpa nama atau tipe gabungan dari tipe-tipe ini tidak boleh digunakan sebagai argumen template untuk parameter tipe template". Saya kira alasannya adalah bahwa kelas-kelas lokal akan membutuhkan banyak informasi lagi untuk dimasukkan ke dalam nama yang rusak, tetapi saya tidak tahu pasti. Tentu saja kompiler tertentu mungkin menawarkan ekstensi untuk menyiasati hal ini, seperti yang terlihat pada MSVC ++ 8 dan versi terbaru g ++.
j_random_hacker
9
Pembatasan ini dicabut di C ++ 11.
Stephan Dollberg
31

Salah satu aplikasi kelas C ++ yang ditentukan secara lokal ada dalam pola desain Pabrik :


// In some header
class Base
{
public:
    virtual ~Base() {}
    virtual void DoStuff() = 0;
};

Base* CreateBase( const Param& );

// in some .cpp file
Base* CreateBase( const Params& p )
{
    struct Impl: Base
    {
        virtual void DoStuff() { ... }
    };

    ...
    return new Impl;
}

Meskipun Anda dapat melakukan hal yang sama dengan namespace anonim.

Nikolai Fetissov
sumber
Menarik! Meskipun batasan terkait template yang saya sebutkan akan berlaku, pendekatan ini menjamin bahwa instance Impl tidak dapat dibuat (atau bahkan dibicarakan!) Kecuali oleh CreateBase (). Jadi ini sepertinya cara terbaik untuk mengurangi sejauh mana klien bergantung pada detail implementasi. +1.
j_random_hacker
26
Itu ide yang bagus, tidak yakin apakah saya akan menggunakannya dalam waktu dekat, tapi mungkin ide yang bagus untuk ditarik ke bar untuk mengesankan beberapa anak ayam :)
Robert Gould
2
(Saya berbicara tentang anak ayam BTW, bukan jawabannya!)
markh44
9
lol Robert ... Ya, tidak ada yang cukup mengesankan seorang wanita seperti mengetahui tentang sudut-sudut yang tidak jelas dari C ++ ...
j_random_hacker
10

Ini sebenarnya sangat berguna untuk melakukan beberapa pekerjaan keamanan-pengecualian berbasis tumpukan. Atau pembersihan umum dari fungsi dengan beberapa titik balik. Ini sering disebut idiom RAII (akuisisi sumber daya adalah inisialisasi).

void function()
{

    struct Cleaner
    {
        Cleaner()
        {
            // do some initialization code in here
            // maybe start some transaction, or acquire a mutex or something
        }

        ~Cleaner()
        {
             // do the associated cleanup
             // (commit your transaction, release your mutex, etc.)
        }
    };

    Cleaner cleaner;

    // Now do something really dangerous
    // But you know that even in the case of an uncaught exception, 
    // ~Cleaner will be called.

    // Or alternatively, write some ill-advised code with multiple return points here.
    // No matter where you return from the function ~Cleaner will be called.
}
simong
sumber
5
Cleaner cleaner();Saya pikir ini akan menjadi deklarasi fungsi daripada definisi objek.
pengguna
2
@user Anda benar. Untuk memanggil konstruktor default, dia harus menulis Cleaner cleaner;atau Cleaner cleaner{};.
callyalater
Kelas di dalam fungsi tidak ada hubungannya dengan RAII dan, selain itu, ini bukan kode C ++ yang valid dan tidak akan dikompilasi.
Mikhail Vasilyev
1
Bahkan di dalam function, class seperti ini BENAR - BENAR adalah tentang RAII di C ++.
Christopher Bruns
9

Nah, pada dasarnya, kenapa tidak? A structin C (kembali ke awal waktu) hanyalah cara untuk mendeklarasikan struktur record. Jika Anda menginginkannya, mengapa tidak dapat mendeklarasikannya di mana Anda akan mendeklarasikan variabel sederhana?

Setelah Anda melakukannya, ingatlah bahwa tujuan C ++ adalah agar kompatibel dengan C jika memungkinkan. Jadi itu bertahan.

Charlie Martin
sumber
semacam fitur yang rapi untuk bertahan, tetapi seperti yang baru saja ditunjukkan oleh j_random_hacker itu tidak berguna seperti yang saya bayangkan di C ++: /
Robert Gould
Ya, aturan pelingkupan juga aneh di C. Saya pikir, sekarang setelah saya memiliki pengalaman 25+ tahun dengan C ++, mungkin berusaha untuk menjadi seperti C mungkin saja adalah sebuah kesalahan. Di sisi lain, bahasa yang lebih elegan seperti Eiffel tidak diadopsi dengan mudah.
Charlie Martin
Ya, saya telah memigrasi basis kode C yang ada ke C ++ (tetapi tidak ke Eiffel).
ChrisW
3

Ini untuk membuat array objek yang diinisialisasi dengan benar.

Saya memiliki kelas C yang tidak memiliki konstruktor default. Saya ingin array objek kelas C. Saya mencari tahu bagaimana saya ingin objek tersebut diinisialisasi, kemudian mendapatkan kelas D dari C dengan metode statis yang memberikan argumen untuk C dalam konstruktor default D:

#include <iostream>
using namespace std;

class C {
public:
  C(int x) : mData(x)  {}
  int method() { return mData; }
  // ...
private:
  int mData;
};

void f() {

  // Here I am in f.  I need an array of 50 C objects starting with C(22)

  class D : public C {
  public:
    D() : C(D::clicker()) {}
  private:
    // I want my C objects to be initialized with consecutive
    // integers, starting at 22.
    static int clicker() { 
      static int current = 22;
      return current++;
    } 
  };

  D array[50] ;

  // Now I will display the object in position 11 to verify it got initialized
  // with the right value.  

  cout << "This should be 33: --> " << array[11].method() << endl;

  cout << "sizodf(C): " << sizeof(C) << endl;
  cout << "sizeof(D): " << sizeof(D) << endl;

  return;

}

int main(int, char **) {
  f();
  return 0;
}

Demi kesederhanaan, contoh ini menggunakan konstruktor non-default sepele dan kasus di mana nilainya diketahui pada waktu kompilasi. Sangat mudah untuk memperluas teknik ini ke kasus di mana Anda ingin array objek diinisialisasi dengan nilai yang hanya diketahui pada waktu proses.

Thomas L Holaday
sumber
Aplikasi yang pasti menarik! Tidak yakin itu bijaksana atau bahkan aman - jika Anda perlu memperlakukan array D itu sebagai array C (misalnya Anda perlu meneruskannya ke fungsi yang mengambil D*parameter) maka ini akan diam-diam rusak jika D sebenarnya lebih besar dari C . (Saya pikir ...)
j_random_hacker
+ j_random_hacker, sizeof (D) == sizeof (C). Saya menambahkan laporan sizeof () untuk Anda.
Thomas L Holaday