Вложенные функции недопустимы, но почему разрешены прототипы вложенных функций? [C ++] - PullRequest
7 голосов
/ 30 мая 2009

Я читал связанный вопрос, который заставляет меня задать этот вопрос.

Рассмотрим следующий код

int main()
{
    string SomeString();
}

Все говорят, компилятор воспринимает это как прототип функции, а не как объект string . Теперь рассмотрим следующий код.

int main()
{
    string Some()
    {
        return "";
    }
}

Компилятор сказал, что это недопустимо, так как я думаю, что определение вложенных функций не допускается. Если это не разрешено, почему разрешены прототипы вложенных функций? Это не дает никакого преимущества, а не создает путаницу (или я здесь упускаю некоторые действительные моменты?)

Я понял, что следующее действительно.

int main()
{ 
  string SomeFun();
  SomeFun();
  return 0;
}

string SomeFun()
{
  std::cout << "WOW this is unexpected" << std::endl;
}

Это также сбивает с толку. Я ожидал, что функция SomeFun () будет иметь область видимости только в main . Но я был неправ. Почему компилятор позволяет компилировать код, как описано выше? Существуют ли ситуации реального времени, когда код, подобный приведенному выше, имеет смысл?

Есть мысли?

Ответы [ 6 ]

8 голосов
/ 30 мая 2009

Ваш прототип - просто " Форвардная декларация ". Пожалуйста, ознакомьтесь со статьей Википедии.

По сути, он сообщает компилятору "не пугайтесь, если метка SomeFun используется таким образом". Но ваш компоновщик отвечает за поиск правильного тела функции.

Вы можете объявить фиктивный прототип, например, 'char SomeFun ()' и используйте его по всему основному. Вы получите ошибку только тогда, когда ваш компоновщик попытается найти тело вашей фиктивной функции. Но ваш компилятор будет крут с ним.

Есть много преимуществ. Вы должны помнить, что тело функции не всегда находится в одном и том же файле исходного кода. Она может находиться в связанной библиотеке. Кроме того, связанная библиотека может иметь определенную «сигнатуру ссылки». Условно определяет, что вы можете даже выбрать правильную сигнатуру ссылки во время сборки, используя ваши прототипы с заданной областью действия. Хотя большинство людей используют указатели функций для это вместо этого.

Надеюсь, это поможет.

7 голосов
/ 30 мая 2009

Как примечание, в C ++ 03 есть обходной способ определения локальных функций. Это требует злоупотребления функцией локального класса:

int main()
{
    struct Local
    {
        static string Some()
        {
            return "";
        }
    };
    std::cout << Local::Some() << std::endl;
}
5 голосов
/ 30 мая 2009

Относительно того, почему ваша декларация

void f() {
    void g(); g();
}

лучше, чем этот

void g();
void f() {
    g();
}

Обычно хорошо, если вы сохраняете объявления настолько локальными, насколько это возможно, чтобы получалось как можно меньше конфликтов имен. Я говорю, что можно спорить о том, действительно ли объявление функции локально (таким образом) действительно удачно, так как я думаю, что все же лучше обычным образом включить его заголовок и затем пойти "обычным" путем, что также не так смущает людей не зная об этом. Иногда полезно также обойти теневую функцию

void f() {
    int g; 
    // oops, ::g is shadowed. But we can work around that
    {
        void g(); g();
    }
}

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

int main() {
    using std::exit;
    exit();
}

В качестве примечания, есть и другие ситуации, например, когда целевая область действия объявления , а не область действия, в которой появляется это объявление. В общем, объект, который вы объявляете, становится членом области действия в котором появляется декларация. Но это не всегда так. Рассмотрим, например, объявления друзей, где это происходит

struct X { friend void f() { std::cout << "WoW"; } };
int main() { void f(); f(); } // works!

Несмотря на то, что объявление функции (и определение!) f произошло в области действия X, сущность (сама функция) стала членом окружающего пространства имен.

5 голосов
/ 30 мая 2009

Это соглашение от C - как и многие - которое приняло C ++.

Возможность объявить функцию внутри другой функции в C - это решение, которое большинство программистов, вероятно, считают достойным сожаления и ненужным. Особенно с современным дизайном ООП, где определения функций сравнительно меньше, чем в C.

Если вы хотите иметь функции, которые существуют только в области действия другой функции, есть два варианта: boost :: lambda и C ++ 1x lambda .

3 голосов
/ 30 мая 2009

Когда вы объявляете прототип, как вы это делаете, вы в основном говорите компилятору дождаться, пока компоновщик его разрешит. В зависимости от того, где вы пишете прототип, применяются правила определения объема. Технически нет ничего плохого в написании прототипа в вашей функции main () (хотя, IMHO, немного более запутанный), это просто означает, что функция известна только в main (). Если бы вы объявили прототип в верхней части исходного файла (или чаще в заголовочном файле), прототип / функция были бы известны во всем источнике.

string foo()
{
  string ret = someString();  // Error
  return ret; 
}

int main(int argc,char**argv)
{
   string someString();
   string s = somestring(); // OK
   ...
}
3 голосов
/ 30 мая 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...