2010-11-21 3 views
27

Из того, что я могу сказать, вы можете начать все действия в конструкторе при создании глобального объекта. Так вам действительно нужна функция main() в C++ или это просто наследие?Вам действительно нужен main() в C++?

Я могу понять, что это можно считать плохой практикой. Я просто спрашиваю из любопытства.

+3

Интересный вопрос. Я никогда не думал, что у меня есть один глобальный объект. Как вы говорите, плохая практика, но тем не менее интересная. –

+0

что-то подобное реализовано в MFC, где у вас есть один экземпляр 'CWinApp' – Andrey

+3

@CwinApp, MFC предоставляет вам основную/winmain. Итак, у вас все еще есть функция main() в MFC. –

ответ

30

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

Если вы идете с «Выполнение начинается в конструкторе моего глобального объекта», остерегайтесь того, что вы задаете себе множество проблем, связанных с порядком конструкций объектов области пространства имен, определенных в разных единицах перевода (так что точка входа? Ответ: у вас будет несколько точек входа, и какая точка входа выполняется в первую очередь не указана!). В C++ 03 вы даже не гарантируете, что cout правильно сконструирован (в C++ 0x у вас есть гарантия, что он до того, как какой-либо код попытается использовать его, если есть предыдущий вариант: <iostream>).

У вас нет этих проблем, и вам не нужно их обойти (это может быть очень сложно), если вы правильно начнете выполнять вещи в ::main.


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

class MyApp { 
public: 
    MyApp(std::vector<std::string> const& argv); 

    int run() { 
     /* code comes here */ 
     return 0; 
    }; 
}; 

IMPLEMENT_APP(MyApp); 

Для пользователя этой системы, она полностью скрыта, что есть main функции, но этот макрос будет на самом деле определить такую ​​основную функцию следующий

#define IMPLEMENT_APP(AppClass) \ 
    int main(int argc, char **argv) { \ 
    AppClass m(std::vector<std::string>(argv, argv + argc)); \ 
    return m.run(); \ 
    } 

Это не связано с проблемой неуказанного порядка строительства, упомянутого выше. Их преимущество состоит в том, что они работают с различными формами точек входа более высокого уровня. Например, программы Windows GUI запускаются в функции WinMain - тогда IMPLEMENT_APP может определить такую ​​функцию на этой платформе.

+1

Что такое C++? –

+0

@bjarkef, который должен был бы составить хороший новый вопрос SO (не зная о реальных дубликатах этого). Но вкратце: это реализация, где не может быть поддержки ОС для файлов, исключений и т. Д. Это, так сказать, минимальный минимум. Ядро ОС может быть написано для того, чтобы быть нацеленным на автономную реализацию. –

+0

Я думаю, что C++ действительно может использовать исключение SystemExit, как у Python. Это было бы намного чище, чем вызов 'exit'. Но, конечно, тогда у вас есть вопрос, какой поток, бросающий данное исключение, действительно имеет значение. – Omnifarious

0

Существуют реализации, где глобальные объекты невозможны или где нетривиальные конструкторы невозможны для таких объектов (особенно в мобильных и встроенных сферах).

+1

Хорошая мысль, но можно предположить, что он говорит о более «нормальной» реализации на C++. –

2

Если у вас построено более одного глобального объекта, нет никаких гарантий относительно того, какой конструктор будет работать первым.

3

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

+0

Согласен, инициализация объектов происходит только в двух точках. Во время загрузки вытягивание EXE или DLL в память и инициализация стека, где сконфигурирована точка входа (основная). – Eric

2

Если вы создаете статический или динамический библиотечный код, вам не нужно самостоятельно определять main, но вы все равно будете работать в какой-либо программе, которая имеет его.

3

Ну, с точки зрения стандарта C++, да, это по-прежнему требуется. Но я подозреваю, что ваш вопрос отличается от этого.

Я думаю, что делать это так, как вы думаете, вызовет слишком много проблем.

Например, во многих средах возвращаемое значение от main указывается как результат выполнения программы в целом. И это было бы очень сложно воспроизвести от конструктора. Некоторый бит кода все равно может вызвать exit, но это похоже на использование goto и пропустит уничтожение чего-либо в стеке. Вы могли бы попытаться исправить ситуацию, исключив при этом специальное исключение, чтобы генерировать код выхода, отличный от 0.

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

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

Я думаю, что эти два вопроса являются убийцами, если вы хотите избавиться от main.

И я не могу думать о языке, который не имеет базового эквивалента main. В Java, например, есть внешнее имя класса, которое вызывается статической функцией main. В Python есть модуль __main__. В perl есть сценарий, который вы укажете в командной строке.

4

Да! Вы можете покончить с главным.

Отказ от ответственности: Вы спрашивали, возможно ли это, а не если это нужно сделать. Это абсолютно не поддерживаемая, плохая идея. Я сделал это сам по причинам, в которые я не попаду, но я не рекомендую это. Моя цель не избавилась от основной, но она тоже может это сделать.

основных шагов заключаются в следующий:

  1. Найти crt0.c в исходном каталоге CRT вашего компилятора.
  2. Добавить crt0.c в свой проект (копия, а не оригинал).
  3. Найти и удалить звонок на главную из crt0.c.

Получение его для компиляции и ссылки может быть затруднено; Насколько сложно зависит от того, какой компилятор и какая версия компилятора.

Добавлено

Я просто сделал это с Visual Studio 2008, так вот точные шаги, которые вы должны предпринять, чтобы заставить его работать с этим компилятором.

  1. Создайте новое консольное приложение C++ Win32 (нажмите кнопку Далее и отметьте Empty Project).
  2. Добавить новый элемент .. Файл C++, но назовите его crt0.c (не .cpp).
  3. Скопируйте содержимое C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src\crt0.c и вставьте в crt0.c.
  4. Найти mainret = _tmain(__argc, _targv, _tenviron); и прокомментировать.
  5. Щелкните правой кнопкой мыши на crt0.c и выберите Свойства.
  6. Set C/C++ -> Общие сведения -> Дополнительные каталоги включений = "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src".
  7. Set C/C++ -> Препроцессор -> Определения препроцессора = _CRTBLD.
  8. Нажмите OK.
  9. Щелкните правой кнопкой мыши название проекта и выберите «Свойства».
  10. Set C/C++ -> Генерация кода -> Библиотека времени выполнения = Multi-threaded Debug (/MTd) (*).
  11. Нажмите OK.
  12. Добавить новый элемент .. Файл C++, назовите его как угодно (app.cpp для этого примера).
  13. Вставьте код ниже в app.cpp и запустите его.

(*) Вы не можете использовать DLL времени выполнения, вам необходимо статически ссылаться на библиотеку времени выполнения.

#include <iostream> 

class App 
{ 
    public: App() 
    { 
     std::cout << "Hello, World! I have no main!" << std::endl; 
    } 
}; 

static App theApp; 

Добавлено

Я удалил superflous вызов выхода и аннотацию о жизни, как я думаю, мы все способны понять последствия удаления основной.

Ультра Necro

Я просто наткнулся на этот ответ и читать как это и возражения Джона Dibling в ниже. Было очевидно, что я не объяснил, что делает вышеуказанная процедура, и почему это действительно удаляет основной из программы целиком.

Джон утверждает, что «в CRT всегда есть главная». Эти слова не совсем правильны, но дух утверждения есть. Main не является функцией, предоставляемой CRT, вы должны добавить ее самостоятельно. Вызов этой функции содержится в функции точки входа CRT.

Точка входа каждой программы C/C++ является функцией в модуле с именем crt0. Я не уверен, что это соглашение или часть спецификации языка, но каждый компилятор C/C++, с которым я столкнулся (что очень много), использует его.Эта функция в основном делает три вещи:

  1. Инициализировать CRT
  2. Позвонить в
  3. Сорвите

В приведенном выше примере, вызов _tmain, но это какой-то макрос магии, чтобы позволить различные формы, которые «основные» могут иметь, некоторые из которых являются VS специфическими в этом случае.

Что делает вышеприведенная процедура, так это удаление модуля crt0 из CRT и замена его на новый. Вот почему вы не можете использовать DLL Runtime, в этой DLL уже есть функция с тем же именем точки входа, что и добавляемое (2). Когда вы статически связываете, CRT представляет собой коллекцию .lib-файлов, а компоновщик позволяет полностью переопределять модули .lib. В этом случае модуль с только одной функцией.

Наша новая программа содержит запасной CRT, минус его модуль CRT0, но с модулем CRT0 нашего собственного создания. Там мы удаляем вызов на главный. Так что нет нигде!

(2) Возможно, вы могли бы использовать DLL runtime, переименовав функцию точки входа в файл crt0.c и изменив точку входа в настройках компоновщика. Однако компилятор не знает об изменении точки входа, и DLL содержит внешнюю ссылку на «главную» функцию, которую вы не предоставляете, поэтому она не будет компилироваться.

+0

Мне это очень нравится! – vy32

+0

Это действительно не избавляет от основного. Главная всегда жила в ЭЛТ здесь. '_tmain' - это то, что называется« main »CRT. Теперь он этого не делает. –

+0

@ Джон: Да? Это устраняет необходимость предоставления вам основного метода, который является предметом исходного запроса. Он делает это не путем скрытия основного в неясном месте, а путем избавления от вызова CRT к основному источнику, который является источником «проблемы» (ошибка компоновщика, которую вы обычно получаете, не определяя основной). – Tergiver

2

Если вы кодируете окна, сделайте не сделайте это.

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

  1. Глобальные конструкторы объектов запускаются во время запуска среды выполнения C.
  2. Код запуска C запущен во время DLLMain библиотеки времени выполнения C DLL
  3. Во время DLLMain вы удерживаете блокировку загрузчика DLL.
  4. Tring для загрузки другой DLL, когда вы держите блокировку загрузчика DLL, приводит к быстрой смерти для вашего процесса.

Компиляция всего вашего приложения в один исполняемый файл не спасет вас - многие вызовы Win32 могут спокойно загружать системные DLL.

Смежные вопросы