Вложенные функции не допускаются, но почему вложенные прототипы функций разрешены? [С++]

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

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

int main()
{
    string SomeString();
}

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

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

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

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

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

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

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

Любые мысли?

Ответ 1

Ваш прототип просто 'Forward Declaration'. Пожалуйста, просмотрите статью в Википедии.

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

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

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

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

Ответ 2

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

int main()
{
    struct Local
    {
        static string Some()
        {
            return "";
        }
    };
    std::cout << Local::Some() << std::endl;
}

Ответ 3

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

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

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

Ответ 4

Что касается вашего объявления

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();
    }
}

Конечно, в С++ мы могли бы вызвать функцию 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

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

Ответ 6

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

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

int main(int argc,char**argv)
{
   string someString();
   string s = somestring(); // OK
   ...
}