Почему я могу определять структуры и классы внутри функции на C ++?

93

Я просто по ошибке сделал что-то подобное на C ++, и это работает. Почему я могу это сделать?

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

    MyStruct s;
    s.somevalue = 5;
}

Теперь, сделав это, я как бы вспомнил, что читал где-то об этом трюке давным-давно, как об инструменте функционального программирования для бедняков на C ++, но я не могу вспомнить, почему это действительно так и где я его читал.

Ответы на любой вопрос приветствуются!

Примечание: хотя при написании вопроса я не получил ссылок на этот вопрос. , текущая боковая панель указывает на это, поэтому я помещу его здесь для справки, в любом случае вопрос другой, но может быть полезен.

Роберт Гулд
источник

Ответы:

73

[РЕДАКТИРОВАТЬ 18/4/2013]: К счастью, упомянутые ниже ограничения были сняты в C ++ 11, поэтому локально определенные классы в конце концов полезны! Спасибо комментатору bamboon.

Возможность определять классы локально бы сделать создание пользовательских функторов (классов с operator()(), например , функцией сравнения для перехода к std::sort()или «петлевым телам» , которые будут использоваться с std::for_each()) гораздо более удобным.

К сожалению, C ++ запрещает использование локально определенных классов с шаблонами , так как они не связаны. Поскольку большинство приложений функторов включают типы шаблонов, которые созданы на основе типа функтора, локально определенные классы не могут использоваться для этого - вы должны определять их вне функции. :(

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

Соответствующая цитата из стандарта:

14.3.1 / 2:. Локальный тип, тип без связи, безымянный тип или тип, составленный из любого из этих типов, не должны использоваться в качестве аргумента-шаблона для параметра-типа шаблона.

j_random_hacker
источник
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 01
9
Это ограничение снято в C ++ 11.
Стефан Доллберг
31

Одно приложение локально определенных классов C ++ находится в шаблоне проектирования Factory :


// 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
(Я говорю о цыпочках, кстати, а не об ответе!)
markh44
9
lol Роберт ... Да, ничто так не впечатляет женщину, как знание о неясных уголках 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 и, кроме того, это недопустимый код C ++ и не будет компилироваться.
Михаил Васильев
1
Даже внутри функций такие классы ТОЧНО, что и суть RAII в C ++.
Кристофер Брунс
9

Ну в принципе, а почему бы и нет? A structв C (восходящий к истокам времен) был просто способом объявить структуру записи. Если он вам нужен, почему бы не объявить его там, где вы бы объявили простую переменную?

Как только вы это сделаете, помните, что цель C ++ - быть совместимой с C, если это вообще возможно. Так оно и осталось.

Чарли Мартин
источник
своего рода изящная функция, которая сохранилась, но, как только что указал j_random_hacker, она не так полезна, как я представлял себе на C ++: /
Роберт Гулд
Да, правила области видимости тоже были странными в Си. Я думаю, что теперь, когда у меня есть более чем 25-летний опыт работы с C ++, это, возможно, было ошибкой, когда я пытался быть так же похож на C, как и они. С другой стороны, более элегантные языки, такие как Eiffel, были приняты не так легко.
Чарли Мартин
Да, я перенес существующую кодовую базу C на C ++ (но не на Eiffel).
ChrisW
5

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

ChrisW
источник
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