Чому я можу визначити структури та класи всередині функції в C ++?


91

Я просто помилково зробив щось подібне в C ++, і це працює. Чому я можу це зробити?

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

    MyStruct s;
    s.somevalue = 5;
}

Тепер, зробивши це, я якось згадав, як колись давно читав про цей трюк як свого роду інструмент функціонального програмування бідної людини на C ++, але я не можу згадати, чому це дійсно, або де я його читав.

Відповіді на будь-яке питання вітаються!

Примітка: Хоча під час написання запитання я не отримав жодного посилання на це питання , поточна бічна панель вказує на нього, тому я поміщу його тут для довідки, в будь-якому випадку питання інше, але може бути корисним.


Відповіді:


71

[EDIT 18/4/2013]: На щастя, згадане нижче обмеження було скасовано в C ++ 11, тому локально визначені класи все-таки корисні! Завдяки коментатору бамбуку.

Можливість локального визначення класів зробила б створення користувальницьких функторів (класів з operator()(), наприклад, функціями порівняння для передачі std::sort()або "тіл циклу", які будуть використовуватися std::for_each()) набагато зручнішими.

На жаль, C ++ забороняє використовувати локально визначені класи з шаблонами , оскільки вони не мають зв’язку. Оскільки більшість програм функторів включають типи шаблонів, які шаблоновані за типом функтора, локально визначені класи для цього використовувати не можна - їх потрібно визначити поза функцією. :(

[РЕДАКТУВАТИ 1/11/2009]

Відповідна цитата зі стандарту:

14.3.1 / 2: .Локальний тип, тип без зв’язку, неназваний тип або тип, складений із будь-якого з цих типів, не повинні використовуватися як аргумент шаблону для параметра типу шаблону.


2
Хоча емпірично, це, здається, працює з MSVC ++ 8. (Але не з g ++.)
j_random_hacker

Я використовую gcc 4.3.3, і, здається, він працює там: pastebin.com/f65b876b . Чи є у вас посилання на те, де стандарт це забороняє? Мені здається, що його легко можна було б створити під час використання.
Catskul

@Catskul: 14.3.1 / 2: "Локальний тип, тип без зв’язку, неназваний тип або тип, складений із будь-якого з цих типів, не повинні використовуватися як шаблон-аргумент для параметра типу шаблону". Я припускаю, що обгрунтування полягає в тому, що місцеві класи потребували б ще однієї групи інформації, яку слід витіснити в неправдиві імена, але я цього точно не знаю. Звичайно, конкретний компілятор може запропонувати розширення, щоб обійти це, як здається, MSVC ++ 8 та останні версії g ++.
j_random_hacker

9
Це обмеження було скасовано в C ++ 11.
Stephan Dollberg,

31

Одне з застосувань локально визначених класів C ++ - це шаблон фабричного проектування :


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

Хоча ви можете зробити те саме з анонімним простором імен.


Цікаво! Незважаючи на те, що обмеження щодо шаблонів, про які я згадав, будуть застосовуватися, цей підхід гарантує, що екземпляри Impl не можуть бути створені (або навіть обговорені!), Окрім CreateBase (). Тож це здається чудовим способом зменшити ступінь залежності клієнтів від деталей реалізації. +1.
j_random_hacker

26
Це чудова ідея, не впевнений, чи скористаюсь я її найближчим часом, але, мабуть, непогана - вийти за бар, щоб справити враження на деяких курчат :)
Роберт Гулд,

2
(Я кажу про курчат BTW, а не про відповідь!)
markh44

9
лол Роберт ... Так, ніщо так не вражає жінку, як знання про незрозумілі куточки C ++ ...
j_random_hacker

10

Насправді це дуже корисно для виконання деяких робіт із забезпечення безпеки на основі стеків. Або загальне очищення від функції з кількома точками повернення. Це часто називають ідіомою RAII (залучення ресурсів - це ініціалізація).

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

5
Cleaner cleaner();Я думаю, це буде декларація функції, а не визначення об'єкта.
користувач

2
@user Ви маєте рацію. Щоб викликати конструктор за замовчуванням, він повинен написати Cleaner cleaner;або Cleaner cleaner{};.
callyalater

Класи всередині функцій не мають нічого спільного з RAII, і, крім того, це не дійсний код С ++ і не буде компілюватися.
Михайло Васильєв

1
Навіть усередині функцій, такі класи, як ТОЧНО, є тим, що таке RAII у C ++.
Крістофер

9

Ну, в основному, чому б і ні? A structв С (повернення до зорі часу) було лише способом оголосити структуру записів. Якщо ви його хочете, чому б не змогти оголосити його там, де ви оголосите просту змінну?

Зробивши це, тоді пам’ятайте, що метою С ++ було бути сумісним із С, якщо це можливо. Так воно і залишилось.


якась акуратна функція, яка збереглася, але, як тільки зазначив j_random_hacker, це не настільки корисно, як я собі уявляв у C ++: /
Роберт Гулд,

Так, правила обсягу були дивними і в C. Думаю, тепер, коли я маю 25+ років досвіду роботи з C ++, можливо, прагнення бути настільки ж схожим на C, як вони, можливо, було помилкою. З іншого боку, більш елегантні мови, такі як Ейфелева, не були прийняті настільки ж охоче.
Чарлі Мартін

Так, я переніс існуючу базу коду С на С ++ (але не на Ейфелеву).
ChrisW

5

Це згадується, наприклад, у розділі "7.8: Локальні класи: класи всередині функцій" http://www.icce.rug.nl/documents/cplusplus/cplusplus07.html, який називає його "локальним класом" і каже " може бути дуже корисним у розширених додатках, що включають успадкування або шаблони ".


3

Він призначений для створення масивів об’єктів, які правильно ініціалізовані.

У мене є клас C, який не має конструктора за замовчуванням. Я хочу масив об'єктів класу C. Я з'ясовую, як я хочу, щоб ці об'єкти ініціалізувались, а потім вивів клас D із C за допомогою статичного методу, який забезпечує аргумент для C у конструкторі 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;
}

Для простоти в цьому прикладі використовується тривіальний конструктор, який не використовується за замовчуванням, і випадок, коли значення відомі під час компіляції. Це просто поширити цю техніку на випадки, коли вам потрібен масив об’єктів, ініціалізований значеннями, які відомі лише під час виконання.


Звичайно, цікавий додаток! Не впевнений, що це розумно чи навіть безпечно - якщо вам потрібно обробляти цей масив D як масив C (наприклад, вам потрібно передати його функції, яка приймає D*параметр), це буде мовчки ламатися, якщо D насправді більше C . (Я думаю ...)
j_random_hacker

+ j_random_hacker, sizeof (D) == sizeof (C). Я додав для вас звіт sizeof ().
Thomas L Holaday,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.