2010-11-21 15 views
27

Da quello che posso dire, puoi dare il via a tutte le azioni di un costruttore quando crei un oggetto globale. Quindi hai davvero bisogno di una funzione main() in C++ o è solo legacy?Hai davvero bisogno di un main() in C++?

Posso capire che potrebbe essere considerato una cattiva pratica farlo. Sto solo chiedendo per curiosità.

+3

Interessante domanda. Non avevo mai pensato di avere un singolo oggetto globale. Come dici tu, cattiva pratica, ma comunque interessante. –

+0

qualcosa come questo è implementato in MFC dove si ha istanza singola di 'CWinApp' – Andrey

+3

@CwinApp, MFC fornisce main/winmain per te. Quindi, hai ancora una funzione main() in MFC. –

risposta

30

Se si desidera eseguire il programma su un'implementazione C++ ospitata, è necessaria una funzione main. Ecco come sono definite le cose. Puoi lasciarlo vuoto se vuoi, ovviamente. Dal punto di vista tecnico, il linker vuole risolvere il simbolo main utilizzato nella libreria di runtime (che non ha la minima idea delle tue intenzioni speciali di ometterlo, ma emette comunque una chiamata). Se lo standard ha specificato che main è facoltativo, ovviamente le implementazioni potrebbero fornire soluzioni, ma ciò dovrebbe avvenire in un universo parallelo.

Se si va con la "esecuzione inizia nel costruttore del mio oggetto globale", attenzione che si imposta da soli fino a molti problemi relativi all'ordine di costruzioni di portata namespace oggetti definiti in diverse unità di traduzione (Che cosa è il punto di ingresso? La risposta è: si avranno più punti di ingresso e quale punto di ingresso viene eseguito per primo non è specificato!). In C++ 03 non è nemmeno garantito che cout sia stato costruito correttamente (in C++ 0x si ha la certezza che lo è, prima che qualsiasi codice tenti di usarlo, a patto che ci sia un precedente contenuto di <iostream>).

Non si dispone di questi problemi e non è necessario aggirarli (cosa che può essere molto complicata) se si avvia correttamente l'esecuzione in ::main.


Come accennato nei commenti, ci sono comunque diversi sistemi che nascondono main dall'utente facendogli dire il nome di una classe che viene istanziato all'interno main. Questo funziona in modo simile al seguente esempio

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

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

IMPLEMENT_APP(MyApp); 

Per l'utente di questo sistema, è completamente nascosto che c'è una funzione main, ma che macro sarebbe effettivamente definire una funzione come principale come segue

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

Questo non ha il problema di un ordine di costruzione non specificato di cui sopra. Il vantaggio di loro è che funzionano con diverse forme di punti di ingresso di livello superiore. Ad esempio, i programmi della GUI di Windows si avviano in una funzione WinMain - IMPLEMENT_APP potrebbe quindi definire tale funzione su tale piattaforma.

+1

cosa è ospitato in C++? –

+0

@bjarkef che avrebbe dovuto creare una bella nuova domanda SO (non a conoscenza di duplicati reali di quello). Ma in breve: è un'implementazione in cui potrebbe non esserci il supporto del sistema operativo per file, eccezioni e così via. È, per così dire, il minimo indispensabile. Un kernel del sistema operativo potrebbe essere scritto per essere destinato a un'implementazione indipendente. –

+0

Penso che C++ possa davvero usare un'eccezione 'SystemExit' come Python. Sarebbe molto più pulito di chiamare "exit". Ma, naturalmente, avete il problema di quale thread lanciare l'eccezione data ha davvero importanza. – Omnifarious

0

Esistono implementazioni in cui non sono possibili oggetti globali o dove non sono possibili costruttori non banali per tali oggetti (in particolare nei domini mobile e embedded).

+1

Un buon punto, ma si presume che stia parlando di un'implementazione C++ più "normale". –

2

Se si dispone di più di un oggetto globale in fase di costruzione, non è possibile garantire quale costruttore eseguirà per primo.

3

In generale, un'applicazione richiede un punto di ingresso e main è il punto di ingresso. Il fatto che l'inizializzazione di globals potrebbe accadere prima di main è praticamente irrilevante. Se stai scrivendo una console o un'app per la GUI devi collegarti a main, ed è una buona pratica avere quella routine come responsabile dell'esecuzione principale dell'app piuttosto che usare altre funzioni per scopi bizzarri non voluti.

+0

Sono d'accordo, l'inizializzazione di oggetti avviene solo in due punti. Al momento del caricamento, richiama EXE o DLL nella memoria e all'inizializzazione dello stack dove è configurato il punto di ingresso (principale). – Eric

2

Se si sta creando un codice di libreria statico o dinamico, non è necessario definire lo standard main, ma si arriverà comunque all'esecuzione in alcuni programmi che lo hanno.

3

Bene, dal punto di vista dello standard C++, sì, è ancora necessario. Ma sospetto che la tua domanda sia di natura diversa da quella.

Penso che farlo nel modo in cui stai pensando causerebbe troppi problemi.

Ad esempio, in molti ambienti il ​​valore di ritorno da main viene indicato come risultato dello stato dall'esecuzione del programma nel suo complesso. E sarebbe davvero difficile replicare da un costruttore. Un po 'di codice potrebbe comunque chiamare exit ovviamente, ma sembra usare un goto e saltare la distruzione di qualsiasi cosa nello stack. Potresti provare a sistemare le cose facendo un'eccezione speciale che hai invece lanciato per generare un codice di uscita diverso da 0.

Ma poi si incontra ancora il problema dell'ordine di esecuzione dei costruttori globali non definiti. Ciò significa che in qualsiasi particolare costruttore per un oggetto globale non sarà possibile formulare alcuna ipotesi sul fatto che esista o meno un altro oggetto globale ancora esistente.

Si potrebbe provare a risolvere il problema dell'ordine del costruttore semplicemente dicendo che ogni costruttore ha il proprio thread e se si desidera accedere a qualsiasi altro oggetto globale è necessario attendere una variabile di condizione fino a quando non dicono che sono costruiti. Però è solo chiedere dei deadlock, e questi deadlock sarebbero davvero difficili da debugare. Avresti anche il problema di quale thread uscire con l'eccezione speciale 'valore di ritorno dal programma' costituirà il vero valore di ritorno del programma nel suo complesso.

Penso che quei due problemi siano assassini se si vuole liberarsi di main.

E non riesco a pensare a un linguaggio che non ha un equivalente di base a main. In Java, ad esempio, esiste un nome di classe fornito esternamente con la chiamata alla funzione statica main. In Python, c'è il modulo __main__. Nello perl c'è lo script specificato sulla riga di comando.

4

Sì! Puoi eliminare main.

Disclaimer: Hai chiesto se fosse possibile, non se fosse necessario. Questa è una pessima idea totalmente non supportata. L'ho fatto io stesso, per ragioni per cui non entrerò, ma non lo raccomanderò. Il mio scopo non era quello di sbarazzarmi di main, ma può farlo anche.

I passaggi fondamentali sono i seguenti:

  1. Trova crt0.c nella directory sorgente CRT del compilatore.
  2. Aggiungi crt0.c al tuo progetto (una copia, non l'originale).
  3. Trova e rimuovi la chiamata al principale da crt0.c.

Compilare e collegare può essere difficile; Quanto difficile dipende da quale compilatore e da quale versione del compilatore.

Aggiunto

ho appena fatto con Visual Studio 2008, per cui qui sono i passaggi esatti si deve prendere per farlo funzionare con quel compilatore.

  1. Creare una nuova applicazione console C++ Win32 (fare clic su Avanti e selezionare Empty Project).
  2. Aggiungere un nuovo elemento .. File C++, ma denominarlo crt0.c (non .cpp).
  3. Copia il contenuto di C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src\crt0.c e incolla nello crt0.c.
  4. Trova mainret = _tmain(__argc, _targv, _tenviron); e commentalo.
  5. Fare clic con il pulsante destro del mouse su crt0.c e selezionare Proprietà.
  6. Imposta C/C++ -> Generale -> Directory di inclusione aggiuntiva = "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src".
  7. Set C/C++ -> Preprocessore -> Definizioni del preprocessore = _CRTBLD.
  8. Fare clic su OK.
  9. Fare clic con il tasto destro del mouse sul nome del progetto e selezionare Proprietà.
  10. Imposta C/C++ -> Generazione codice -> Libreria di runtime = Multi-threaded Debug (/MTd) (*).
  11. Fare clic su OK.
  12. Aggiungere un nuovo elemento .. File C++, denominarlo in qualsiasi modo (app.cpp per questo esempio).
  13. Incollare il codice di seguito in app.cpp ed eseguirlo.

(*) Non è possibile utilizzare la DLL di runtime, è necessario collegare staticamente alla libreria di runtime.

#include <iostream> 

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

static App theApp; 

Aggiunto

ho rimosso la chiamata uscita superfluo e la fascetta pubblicitaria sulla vita come penso che siamo tutti in grado di comprendere le conseguenze della rimozione principale.

Ultra Necro

Ho appena imbattuto in questa risposta e leggere sia esso e le obiezioni di John Dibling qui sotto. Era chiaro che non ho spiegato cosa fa la procedura sopra e perché questo rimuove completamente il main dal programma.

John afferma che "c'è sempre un main" nel CRT. Quelle parole non sono strettamente corrette, ma lo spirito dell'affermazione è. Main non è una funzione fornita dal CRT, devi aggiungerla tu stesso. La chiamata a quella funzione è nella funzione del punto di ingresso fornita da CRT.

Il punto di ingresso di ogni programma C/C++ è una funzione in un modulo denominato 'crt0'. Non sono sicuro che questa sia una convenzione o parte delle specifiche del linguaggio, ma ogni compilatore C/C++ che ho incontrato (che è molto) lo usa.Questa funzione fa fondamentalmente tre cose:

  1. inizializzare la CRT
  2. chiamata principale
  3. abbattere

Nell'esempio precedente, la chiamata viene _tmain ma che è un po 'di magia macro per consentire le varie forme che può avere "main", alcune delle quali sono specifiche per VS in questo caso.

L'operazione sopra descritta rimuove il modulo 'crt0' dal CRT e lo sostituisce con uno nuovo. Questo è il motivo per cui non è possibile utilizzare la DLL di runtime, esiste già una funzione in quella DLL con lo stesso nome del punto di ingresso di quello che stiamo aggiungendo (2). Quando si collega staticamente, il CRT è una raccolta di file .lib e il linker consente di ignorare completamente i moduli .lib. In questo caso un modulo con una sola funzione.

Il nostro nuovo programma contiene il CRT di serie, meno il suo modulo CRT0, ma con un modulo CRT0 di nostra creazione. Lì rimuoviamo la chiamata al main. Quindi non c'è main ovunque!

(2) Si potrebbe pensare di poter utilizzare la DLL di runtime rinominando la funzione del punto di ingresso nel file crt0.c e modificando il punto di ingresso nelle impostazioni del linker. Tuttavia, il compilatore non è al corrente della modifica del punto di ingresso e la DLL contiene un riferimento esterno a una funzione "principale" che non viene fornita, quindi non verrà compilata.

+0

Mi piace davvero tanto! – vy32

+0

Questo non elimina veramente il main. Main ha sempre vissuto nel CRT qui. '_tmain' è proprio ciò che il' main' di CRT ha chiamato. Ora non lo fa. –

+0

@ John: Huh? Elimina la necessità per te di fornire un metodo principale che è l'oggetto della query originale. Lo fa, non nascondendo un main in un luogo oscuro, ma eliminando la chiamata CRT al main che è la fonte del "problema" (l'errore del linker che normalmente otterresti non definendo main). – Tergiver

2

Se si sta codificando per Windows, fare non fare questo.

L'esecuzione della tua app interamente dal costruttore di un oggetto globale può funzionare bene per un bel po ', ma prima o poi si effettuerà una chiamata alla funzione sbagliata e si concluderà con un programma che termina senza preavviso.

  1. I costruttori di oggetti globali vengono eseguiti durante l'avvio del runtime C.
  2. Il codice di avvio runtime C viene eseguito durante il DLLMain della DLL di runtime C
  3. Durante DLLMain, si dispone del blocco del caricatore DLL.
  4. Tring per caricare un'altra DLL mentre già è in possesso del blocco del caricatore DLL comporta una rapida morte per il processo.

La compilazione dell'intera app in un singolo file eseguibile non consente di salvarvi: molte chiamate Win32 hanno il potenziale per caricare in modo silenzioso le DLL di sistema.

Problemi correlati