C ++ 'da bir işlev içindeki yapıları ve sınıfları neden tanımlayabilirim?


93

C ++ 'da yanlışlıkla böyle bir şey yaptım ve işe yarıyor. Bunu neden yapabilirim?

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

    MyStruct s;
    s.somevalue = 5;
}

Şimdi bunu yaptıktan sonra, uzun zaman önce bir yerde, bir çeşit fakir adamın C ++ için işlevsel programlama aracı olarak bu numara hakkında okuduğumu hatırladım, ancak bunun neden geçerli olduğunu veya nerede okuduğumu hatırlayamıyorum.

Her iki soruya da cevap verebilirsiniz!

Not: soruyu yazarken ben ilişkin referansları alamadım rağmen bu soruya , şimdiki yan çubuk noktaları dışarı ben, başvuru için burada soru farklıdır her iki şekilde koyacağım ama yararlı olabilir böylece.


Yanıtlar:


74

[DÜZENLEME 18/4/2013]: Neyse ki , aşağıda belirtilen kısıtlama C ++ 11'de kaldırıldı, bu nedenle yerel olarak tanımlanmış sınıflar her şeyden önce kullanışlıdır! Yorumcu bambusuna teşekkürler.

Yerel sınıfları tanımlar yeteneği olur (bir sınıfları özel fanktorlar oluşturulabilmesi operator()()için geçme, örneğin karşılaştırma işlevlerini std::sort()ile kullanılacak olan ya da "döngü organları" std::for_each()) çok daha uygun.

Ne yazık ki, C ++, yerel olarak tanımlanmış sınıfların şablonlarla kullanılmasını yasaklar çünkü bunların bağlantıları yoktur. Functor uygulamalarının çoğu, functor türüne göre şablon türlerini içerdiğinden, bunun için yerel olarak tanımlanmış sınıflar kullanılamaz - bunları işlevin dışında tanımlamalısınız. :(

[1/11/2009 DÜZENLE]

Standarttan ilgili alıntı:

14.3.1 / 2 :. Yerel bir tür, bağlantısız bir tür, adsız bir tür veya bu türlerin herhangi birinden birleştirilmiş bir tür, bir şablon türü parametresi için şablon argümanı olarak kullanılmayacaktır.


2
Ampirik olarak, bu MSVC ++ 8 ile çalışıyor gibi görünüyor. (Ama g ++ ile değil.)
j_random_hacker

Gcc 4.3.3 kullanıyorum ve orada çalışıyor gibi görünüyor: pastebin.com/f65b876b . Standardın nerede yasakladığına dair bir referansınız var mı? Bana, kullanım sırasında kolayca örneklenebilecek gibi görünüyor.
Catskul

@Catskul: 14.3.1 / 2: "Yerel bir tür, bağlantısız bir tür, adsız bir tür veya bu türlerin herhangi birinden birleştirilmiş bir tür, bir şablon türü parametresi için şablon argümanı olarak kullanılmayacaktır". Sanırım mantık, yerel sınıfların karıştırılmış adlara itilmesi için başka bir bilgi demeti gerektireceğidir, ancak bunu kesin olarak bilmiyorum. Elbette belirli bir derleyici, MSVC ++ 8 ve son g ++ sürümlerinin yaptığı gibi, bunu aşmak için uzantılar sunabilir.
j_random_hacker

10
Bu kısıtlama C ++ 11'de kaldırıldı.
Stephan Dollberg

31

Yerel olarak tanımlanmış C ++ sınıflarının bir uygulaması Fabrika tasarım modelindedir :


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

Anonim ad alanıyla da aynısını yapabilirsiniz.


İlginç! Bahsettiğim şablonlarla ilgili kısıtlamalar geçerli olacak olsa da, bu yaklaşım, Impl örneklerinin CreateBase () dışında yaratılamayacağını (hatta hakkında konuşulamayacağını) garanti eder. Dolayısıyla bu, istemcilerin uygulama ayrıntılarına bağlı olma kapsamını azaltmanın mükemmel bir yolu gibi görünüyor. +1.
j_random_hacker

26
Ben yakın zamanda bunu kullanarak olacak, ancak muhtemelen iyi bir bazı civciv :) etkilemek için barda çekilme eğer bu değil tabii, düzgün bir fikir
Robert Gould

2
(BTW civcivlerinden bahsediyorum, cevaptan değil!)
markh44

10
lol Robert ... Evet, hiçbir şey bir kadını C ++ 'nın belirsiz köşelerini bilmek kadar etkileyemez ...
j_random_hacker

10

Aslında bazı yığın tabanlı istisna güvenliği çalışmaları yapmak için çok kullanışlıdır. Veya birden çok dönüş noktasına sahip bir işlevden genel temizlik. Buna genellikle RAII (kaynak edinimi başlatma) deyimi denir.

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

6
Cleaner cleaner();Bunun bir nesne tanımından çok işlev bildirimi olacağını düşünüyorum.
kullanıcı

2
@user Haklısınız. Varsayılan kurucuyu çağırmak için Cleaner cleaner;veya yazması gerekir Cleaner cleaner{};.
callyalater

Fonksiyonların içindeki sınıfların RAII ile ilgisi yoktur ve ayrıca bu geçerli bir C ++ kodu değildir ve derlenmeyecektir.
Mikhail Vasilyev

1
İşlevlerin içinde bile, bunun gibi sınıflar KESİNLİKLE C ++ 'daki RAII ile ilgilidir.
Christopher Bruns

9

Temel olarak, neden olmasın? BirstructC'deki (zamanın başlangıcına geri dönerek) sadece bir kayıt yapısını ilan etmenin bir yoluydu. Eğer bir tane istiyorsanız, neden onu basit bir değişken tanımlayacağınız yerde açıklayamıyorsunuz?

Bunu yaptıktan sonra, C ++ hedefinin mümkünse C ile uyumlu olmak olduğunu unutmayın. Yani kaldı.


hayatta kalmak için güzel bir özellik, ancak j_random_hacker'ın az önce belirttiği gibi, C ++ 'da hayal ettiğim kadar kullanışlı değil: /
Robert Gould,

Evet, kapsam belirleme kuralları C'de de tuhaftı. Sanırım artık C ++ ile 25 yıldan fazla deneyimim olduğuna göre, bu belki de C gibi olmaya çabalamak bir hata olabilirdi. Öte yandan, Eyfel gibi daha zarif diller hemen hemen benimsenmemişti.
Charlie Martin

Evet, mevcut bir C kod tabanını C ++ 'ya taşıdım (ancak Eiffel'e değil).
ChrisW


3

Düzgün bir şekilde başlatılmış nesne dizileri oluşturmak içindir.

Varsayılan kurucusu olmayan bir C sınıfım var. C sınıfı nesnelerden oluşan bir dizi istiyorum. Bu nesnelerin nasıl başlatılmasını istediğimi anlıyorum, sonra C'den D'nin varsayılan yapıcısındaki C için bağımsız değişken sağlayan statik bir yöntemle bir D sınıfı türetiyorum:

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

Basitlik uğruna, bu örnek önemsiz, varsayılan olmayan bir kurucu ve değerlerin derleme zamanında bilindiği bir durum kullanır. Bu tekniği, yalnızca çalışma zamanında bilinen değerlerle başlatılan bir dizi nesne istediğiniz durumlara genişletmek basittir.


Kesinlikle ilginç bir uygulama! Yine de akıllıca ve hatta güvenli olduğundan emin değilim - eğer D dizisini bir C dizisi olarak ele almanız gerekiyorsa (örneğin, onu bir D*parametre alan bir işleve geçirmeniz gerekiyorsa), D gerçekten C'den büyükse bu sessizce bozulacaktır. . (Sanırım ...)
j_random_hacker

+ j_random_hacker, sizeof (D) == sizeof (C). Sizin için sizeof () raporu ekledim.
Thomas L Holaday
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.