Muss es denn immer eine Klasse sein?

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
JannBerger
Beiträge: 5
Registriert: 22.12.2010, 11:38
Echter Name: Jann Berger

Muss es denn immer eine Klasse sein?

Beitrag von JannBerger »

Hallo zusammen.

Nun ich muss für den Informatik-Kurs an meiner Schule ein kleines Programm schreiben und ich habe mich für ein simples DirectX Spiel entschieden da ich schon immer wissen wollte wie das funktioniert.
Im Kurs selber haben wir C++ sowie Java unter die Lupe genommen (Wohl eher die Oberfläche angekratzt), also habe ich mir ein Buch über C/C++ gekauft und mich in die DX SDKs von Microsoft vertieft.
Zu meiner Freude habe ich mittlerweile genügend Kenntnisse, um extrem simples kleines Spiel zu programmieren. Nun aber geht es an die Implementation. Da aber hackt es bei mir. Das Problem ist, das mein Lehrer
eine andere Vorstellung von C++ hat als ich, ich ihm aber nicht widersprechen möchte da er doch schon Jahre an Erfahrung (Hat bei einer Software-Firma gearbeitet) angehäuft hat.

Seiner Meinung nach gehört so ziemlich alles in eine Klasse (Objektorientiert). Da ich aber ein wirklich kleines Programm schreibe und dieses ja nicht in Form einer API weitergeben werde, habe ich mir gedacht,
mache es so einfach wie möglich, so effektiv wie möglich. Nun zuerst zu meinem Code:

// Main.cpp

Code: Alles auswählen

#include "Application.h"

namespace Win32 {

HWND InitializeWindow(HINSTANCE instanceHandle, Application* application);
UINT FlushMessages();
LONGLONG GetTimerFrequency();
LONGLONG GetTimerCounter();

LRESULT CALLBACK WindowProcedure(
    HWND windowHandle,
    UINT message,
    WPARAM wordParameter,
    LPARAM longParameter);

} // namespace Win32

//
// Der Haupteinstiegspunkt der Anwendung.
// Parameter instanceHandle: Die Programminstanz.
// Parameter previousInstanceHandle: Die vorherige Programminstanz.
// Parameter commandLine: Die übergebene Startargumente.
// Parameter windowAppearance: Gibt an wie das Fenster beim Start angezeigt werden soll.
// Rückgabewert: Ein Statuscode an das Betriebssystem.
//
int WINAPI wWinMain(
    HINSTANCE instanceHandle,
    HINSTANCE previousInstanceHandle,
    WCHAR* commandLine,
    int windowAppearance)
{
    HWND windowHandle = NULL;

    try
    {
        // Wird beim verlassen des try-Blocks automatisch freigegeben.
        std::auto_ptr<Application> application(new Application());

        // Dem Fenster die erstellte Anwendungsinstanz übergeben.
        windowHandle = Win32::InitializeWindow(instanceHandle, application.get());                  

        // Das erstellte Fensterhandle der Anwendung übergeben noch bevor das Fenster
        // angezeigt wird. Somit kann diese intern noch die Fenstergrösse überschreiben.
        application->Initialize(windowHandle);

        ::ShowWindow(windowHandle, SW_SHOWNORMAL);
        ::SetForegroundWindow(windowHandle);

        LONGLONG frequency = Win32::GetTimerFrequency();
        LONGLONG lastTime = Win32::GetTimerCounter();
        LONGLONG currentTime = 0;
        double elapsedTime = 0.0;

        while (Win32::FlushMessages() != WM_QUIT)
        {
            // Die verstrichene Zeit zwischen zwei Durchgängen in Millisekunden berechnen.
            currentTime = Win32::GetTimerCounter();
            elapsedTime = static_cast<double>(currentTime - lastTime) / frequency;
            lastTime = currentTime;
 
            // Zu grosse Zeitsprünge z.B. nach einer Fenstermanipulation verwerfen.
            if (elapsedTime > 0.25)
                elapsedTime = 0.0;

            application->Idle(elapsedTime);
        }

        windowHandle = NULL;
    }
    catch (const std::exception& reason)
    {
        // Ein noch bestehendes Fenster ordnungsgemäss beenden.
        if (windowHandle)
        {
            ::PostMessageW(windowHandle, WM_CLOSE, 0, 0);
            Win32::FlushMessages();
        }

        ::MessageBoxA(NULL, reason.what(), "Error", MB_ICONERROR | MB_SETFOREGROUND);
        return 1;
    }

    return 0;
}

namespace Win32 {

//
// Initialisiert das Fenster.
// Parameter instanceHandle: Die Programminstanz.
// Parameter application: Die Anwendungsinstanz.
// Rückgabewert: Das erstellte Fensterhandle.
//
HWND InitializeWindow(HINSTANCE instanceHandle, Application* application)
{
    WNDCLASSEX windowClass;
    windowClass.cbSize = sizeof(windowClass);
    windowClass.cbClsExtra = 0;
    windowClass.cbWndExtra = 0;
    windowClass.hbrBackground = reinterpret_cast<HBRUSH>(::GetStockObject(BLACK_BRUSH));
    windowClass.hCursor = ::LoadCursorW(NULL, IDC_ARROW);
    windowClass.hIcon = ::LoadIconW(NULL, IDI_APPLICATION);
    windowClass.hIconSm = windowClass.hIcon;
    windowClass.hInstance = instanceHandle;
    windowClass.lpfnWndProc = WindowProcedure;
    windowClass.lpszClassName = L"MainWindowClass";
    windowClass.lpszMenuName = NULL;
    windowClass.style = CS_DBLCLKS;

    if (!::RegisterClassExW(&windowClass))
        throw std::exception("::RegisterClassExW failed!");

    // Ein Fenster mit einer fixen Fenstergrösse verwenden.
    DWORD windowStyle = WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX;

    // Die Fensterklientgrösse auf die Hälfte der Bildschirmgrösse setzen.
    LONG screenWidth = static_cast<LONG>(::GetSystemMetrics(SM_CXSCREEN));
    LONG screenHeight = static_cast<LONG>(::GetSystemMetrics(SM_CYSCREEN));

    RECT windowRectangle;
    windowRectangle.left = 0;
    windowRectangle.top = 0;
    windowRectangle.right = static_cast<LONG>(screenWidth / 2);
    windowRectangle.bottom = static_cast<LONG>(screenHeight / 2);

    ::AdjustWindowRect(&windowRectangle, windowStyle, FALSE);

    LONG windowWidth = windowRectangle.right - windowRectangle.left;
    LONG windowHeight = windowRectangle.bottom - windowRectangle.top;

    // Das Fenster in der Bildschirmmitte positionieren.
    HWND windowHandle = ::CreateWindowW(
        L"MainWindowClass",
        L"Client",
        windowStyle,
        static_cast<int>((screenWidth - windowWidth) / 2),
        static_cast<int>((screenHeight - windowHeight) / 2),
        static_cast<int>(windowWidth),
        static_cast<int>(windowHeight),
        NULL,
        NULL,
        instanceHandle,
        application);

    if (!windowHandle)
        throw std::exception("::CreateWindowW failed!");

    return windowHandle;
}

//
// Verarbeitet alle Nachrichten auf dem Nachrichtenstapel.
// Rückgabewert: Die zuletzt verarbeitet Nachricht.
//
UINT FlushMessages()
{
    MSG message;
    message.message = 0;

    // Die Nachrichten anschliessend vom Nachrichtenstapel entfernen.
    while (::PeekMessageW(&message, NULL, 0, 0, PM_REMOVE))
    {
        if (message.message == WM_QUIT)
            break;

        ::TranslateMessage(&message);
        ::DispatchMessageW(&message);
    }

    return message.message;
}

//
// Ermittelt die Timerfrequenz.
// Rückgabewert: Die Timerfrequenz.
//
LONGLONG GetTimerFrequency()
{
    LARGE_INTEGER frequency;
    ::QueryPerformanceFrequency(&frequency);

    return frequency.QuadPart;
}

//
// Ermittelt den Timerzähler.
// Rückgabewert: Der Timerzähler.
//
LONGLONG GetTimerCounter()
{
    LARGE_INTEGER counter;
    ::QueryPerformanceCounter(&counter);

    return counter.QuadPart;
}

//
// Leitet die Fensternachrichten an die Anwendung weiter.
// Parameter windowHandle: Das entsprechene Fenster der Nachricht.
// Parameter message: Die Nachricht.
// Parameter wordParameter: Der erste Nachrichtenparameter.
// Parameter longParameter: Der zweite Nachrichtenparameter.
// Rückgabewert: Das Resultat der verarbeiteten Nachricht.
//
LRESULT CALLBACK WindowProcedure(
    HWND windowHandle,
    UINT message,
    WPARAM wordParameter,
    LPARAM longParameter)
{
    if (message == WM_NCCREATE)
    {
        // Die Anwendungsinstanz ermitteln und speichern.
        ::SetWindowLongPtrW(
            windowHandle,
            GWLP_USERDATA, 
            reinterpret_cast<LONG_PTR>(
            reinterpret_cast<LPCREATESTRUCT>(longParameter)->lpCreateParams));               
    }

    // Alle Nachrichten an die Anwendungsinstanz weiterleiten.
    Application* application = reinterpret_cast<Application*>(
        ::GetWindowLongPtrW(windowHandle, GWLP_USERDATA));

    return application->WindowProcedure(
        windowHandle, 
        message, 
        wordParameter, 
        longParameter);
}

} // namespace Win32
Application.h

Code: Alles auswählen

#ifndef APPLICATION_H_INCLUDED
#define APPLICATION_H_INCLUDED

#include <exception>
#include <memory>
#include <sstream>
#include <Windows.h>

//
// Die Hauptanwendung.
//
class Application
{
public:
    Application();
    ~Application(); 

    void Initialize(HWND windowHandle);
    void Idle(double elapsedTime);

    LRESULT WindowProcedure(
        HWND windowHandle,
        UINT message,
        WPARAM wordParameter,
        LPARAM longParameter);

private:
    HWND mWindowHandle;
};

#endif // APPLICATION_H_INCLUDED
Application.cpp

Code: Alles auswählen

#include "Application.h"

//
// Konstruktor.
//
Application::Application() : mWindowHandle(NULL)
{
}

//
// Destruktor.
//
Application::~Application() 
{
}

//
// Initialisiert die Anwendung.
// Parameter windowHandle: Das zu verwendende Fenster.
//
void Application::Initialize(HWND windowHandle) 
{
    mWindowHandle = windowHandle;
    ::SetWindowTextW(mWindowHandle, L"Application: 0");
}

//
// Aktualisiert und rendert die Anwendung.
// Parameter elapsedTime: Die verstrichene Zeit seit dem letzten Aufruf.
//
void Application::Idle(double elapsedTime) 
{
    static double tick = 0.0;
    static double time = 0.0;

    tick += elapsedTime;
    time += elapsedTime;

    // Jede Sekunde den Fenstertext aktualisieren.
    if (tick >= 1.0)
    {
        tick = 0.0;
        std::wstringstream format;
        format << L"Application: " << time;
        ::SetWindowTextW(mWindowHandle, format.str().c_str());
    }
}

//
// Verarbeitet die Fensternachrichten.
// Parameter windowHandle: Das entsprechene Fenster der Nachricht.
// Parameter message: Die Nachricht.
// Parameter wordParameter: Der erste Nachrichtenparameter.
// Parameter longParameter: Der zweite Nachrichtenparameter.
// Rückgabewert: Das Resultat der verarbeiteten Nachricht.
//
LRESULT Application::WindowProcedure(
    HWND windowHandle,
    UINT message,
    WPARAM wordParameter,
    LPARAM longParameter)
{
    switch (message)
    {
    case WM_DESTROY:
        ::PostQuitMessage(0);
        return 1;
    }

    return ::DefWindowProcW(windowHandle, message, wordParameter, longParameter);
}
Ich habe mich gefragt, warum kompliziert das Fenster, was ja in C implementiert ist, mühsam in eine Klasse packen wenn ich die paar Funktionen nicht auch in einen Namensraum packen kann.
Auch hat er uns immer gesagt, benutzt keine globalen Variablen usw. Nun, dieses Problem habe ich umgangen in dem ich ein wenig tiefer in der WinApi gesucht habe. Nun übergebe ich einfach eine Instanz meiner
Anwendung an das Fenster. Tja, er ist aber immer noch nicht zufrieden da ich nun mehrere male einen reinterpret_cast anwenden muss. Gäbe es in meinem Beispiel oben ein Problem wenn ich von einer 32-Bit
später auf eine 64-Bit Plattform wechseln würde? Auch verwundert hat mich, dass er noch nie was von einem auto_ptr gehört geschweige denn jemals benutzt hat. Sowieso ist mir aufgefallen, dass er das Thema STL nicht erwähnt hat.
Auch zum Thema Exceptions meinte er, ich solle diese nicht verwenden da wenn ich z.B. eine Ausnahme in meiner WindowProcedure auslöse, das Fenster nicht freigegeben werde usw.
Aber in meinem catch-Block zerstöre ich ein eventuell noch bestehendes Fenster mittels eines WM_CLOSE Befehls. Auch leere ich noch den Nachrichtenstapel.

Tja, ich habe lange im Internet gesucht und bin auf dieses Forum gestossen und möchte Fragen, was ihr dazu denkt.
Der Punkt ist, ich bin ein absoluter Anfänger und möchte sicher nicht einem erfahrenen Programmierer wiedersprechen. Aber ich finde, meine Implementation oben durchaus zu gebrauchen ;)
Was meint ihr?

LG
Und frohe Festtage allerseits
Zuletzt geändert von JannBerger am 22.12.2010, 12:53, insgesamt 1-mal geändert.
Despotist
Establishment
Beiträge: 394
Registriert: 19.02.2008, 16:33

Re: Muss es denn immer eine Klasse sein?

Beitrag von Despotist »

Wenn das Programm fehlerfrei läuft und tut was es soll gibt es kein richtig oder falsch programmiert sondern höchstens ein elegant oder ungeschickt. Wenn du aus deinem Programm per schrittweiser Erweiterung kein Top-Spiel entwickeln willst oder im Team mit anderen arbeitest (die Aufgabe ist sehr eng gesteckt) brauchst du dir über die meisten Konzepte von OOP auch keine Gedanken machen.
Programmieren hat auch bei vielen einen Hauch von Religion und dementschprechend kochen da Emotionen hoch wenn jemand etwas "falsch" macht. Das heißt aber nur derjenige macht es anders aber nicht zwangsläufig in jeder Hinsicht besser oder schlechter. Zumal dein Lehrer ja selbst nicht so toll informiert zu sein scheint wenn er AutoPtr nicht kennt.
An deiner Stelle würde ich also mein Ding machen und seine Vorschläge überdenken ob sie mir nützen. Wenn du auch ohne auskommst auch gut. Er soll euch ja das Programmieren allgemein beibringen und nicht seinen sehr speziellen Stil. Und normalerweise wird ja eine klare Aufgabe gestellt und es lässt sich absehen inwieweit sie erfüllt wurde. Wie du sie löst sollte ja deine Sache sein und eine Lösung abseits des "normalen" Weges zu finden zeugt ja mitunter von mehr Kreativität und Wissen als das was alle machen.
Für deinen Code und deine speziellen Probeleme hab ich zu wenig Zeit und Muse ;).
odenter
Establishment
Beiträge: 207
Registriert: 26.02.2009, 11:58

Re: Muss es denn immer eine Klasse sein?

Beitrag von odenter »

Das Problem ist das der Lehrer am Ende Recht hat und es eben im Zweifel eine schlechte Note für das nichterfüllen der Aufgabe gibt.
Ich kenne den Lehrer nicht, aber wenn er keine Lust hat sich zu informieren dann macht er das auch nicht und verlangt das was er mal gelernt hat.

Die Aufgabenstellung ist also ein Programm zu schreiben in c++ und das ganze muss objektorientiert sein?

Und grundsätzlich zur Frage, eine Klasse muss es theoretisch immer sein, allerdings muss diese nicht zwingend instanziert werden. Manchmal ist ne statische Methode sinniger.
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: Muss es denn immer eine Klasse sein?

Beitrag von Aramis »

Hallo und herzlich Willkommen im Forum!
Auch zum Thema Exceptions meinte er, ich solle diese nicht verwenden da wenn ich z.B. eine Ausnahme in meiner WindowProcedure auslöse, das Fenster nicht freigegeben werde usw.
Nun, das Problem ist dass die Wndproc indirekt aufgerufen wird im Zuge der Verarbeitung der Message–Pump durch Windows (PeekMessage). Insbesondere liegt damit C–Code dazwischen, der zudem auch noch in einem anderen Binaermodul liegt.

Damit ist nicht garantiert dass das Stack Unwinding sauber funktioniert. Insofern wuerde ich tatsaechlich auf das Durchreichen von Exceptions an dieser Stelle verzichten. Lieber Exceptions lokal in der WNDPROC fangen, Fenster beenden, Statuscode zurueckreichen, in der Main ggf. verarbeiten.

Exceptions sind in C++ nicht nur verkannt, sondern auch tatsaechlich gefaehrlich – wenn man sie falsch einsetzt. Im Prinzip muss jede einzelne Funktion auf dem Callstack mit Exceptions im Hinterkopf designt worden sein, sprich sie muss dem RAII–Idiom folgen.

Leider gibt es genug C++–ler, die davon noch nie was gehoert haben, egal wie lange sie die Sprache schon einsetzen. Das betrifft insbesondere Leute mit Java–Background :-)
Tja, er ist aber immer noch nicht zufrieden da ich nun mehrere male einen reinterpret_cast anwenden muss. Gäbe es in meinem Beispiel oben ein Problem wenn ich von einer 32-Bit
später auf eine 64-Bit Plattform wechseln würde?
Nein. Denn genau dafuer wurde ::GetWindowLongPtr ueberhaupt erst eingefuehrt. Der GWLP_USERDATA–Slot ist einen void*–breit und damit generell in der Lage einen Pointer auf T zu halten. Weiterhin ist LONG_PTR lt. Doku:
A LONG_PTR is a long type used for pointer precision. It is used when casting a pointer to a long type to perform pointer arithmetic.
Der reinterpret_cast ist also nicht nur unvermeidbar, sondern auch guter Stil im Vergleich zu einem ordinaeren C–Cast. Cheers :-)

(Wenn du eigene Schnittstellen designt, sollten solche „Hacks“ natuerlich von Grund auf vermieden werden – das WinAPI ist aber nunmal eher auf C ausgerichtet und vor zig Dekaden designt worden. WinAPI von C++ aus zu benutzen wird immer haesslich sein. Das ist kein Grund es nicht zu nutzen, aber ein Grund es moeglichst schnell unter eleganteren, idiomatischeren Wrappern verschwinden zu lassen).

Code: Alles auswählen

catch (const std::exception& reason)
    {
        // Ein noch bestehendes Fenster ordnungsgemäss beenden.
        if (windowHandle)
        {
            ::PostMessageW(windowHandle, WM_CLOSE, 0, 0);
            Win32::FlushMessages();
        }

        ::MessageBoxA(NULL, reason.what(), "Error", MB_ICONERROR | MB_SETFOREGROUND);
        return 1;
    }

Wenn das Application–Objekt formal das Windows–Handle in seinen Besitz uebergehen laesst sobald Initialize aufgerufen wurde, ist das unnoetig – der Application Destruktor ist verantwortlich fuer das Aufraeumen des Fensters. Manuelles Aufraeumen ist grundsaetzlich ein Indiz fuer ein fehlerhaftes Design. Hier bedingt durch die – aus C++–Sicht – sehr unidiomatische Weise wie das WinAPI Fenster verwaltet. Daher nochmal: wenn in der WNDPROC was schiefgeht, Fenster schliessen, Statuscode zurueckreichen.
Ich habe mich gefragt, warum kompliziert das Fenster, was ja in C implementiert ist, mühsam in eine Klasse packen wenn ich die paar Funktionen nicht auch in einen Namensraum packen kann.
Richtig. Die Neigung alles in Klassen zu packen ist eine weit verbreitete Krankheit. Leider gibt es keine allgemeingueltige Antwort was wann richtig ist – nur ein paar Fragen, die du dir immer auf's neue stellen musst …
  • Koennte es sein dass die Funktionalitaet mehrfach benoetigt wird? (Beim Hauptfenster eines Spiels, das SOWIESO im Vollbildmodus laeuft, lautet die Antwort beispielsweise: voraussichtlich nein).
  • laesst sich die Funktionalitaet elegant in ein Objekt, d.h. eine Entitaet mit einer genau definierten Lebensdauer, verpacken?
  • Ist es sinnvoll die Daten in einem Objekt zu verpacken oder sie immer manuell an ungebundene Funktionen durchzureichen … das ist vielfach auch eine reine Stilfrage.
  • Lohnt sich der Mehraufwand fuer das zu erreichende Ziel (es macht beispielsweise einen Unterschied ob du schnell einen Prototypen bastelst oder den Grundstein fuer eine komplexe Applikation legst, an der du moeglicherweise die naechsten 10 Jahre arbeiten wirst).
Eine Application–Klasse ist meiner Meinung nach etwas voellig unnoetiges. Es wird nie mehr als eine Instanz davon geben, wieso sollte man dann ueberhaupt eine solche Klasse basteln?

Globale Daten sind evil. Das ist eine gute Faustregel, mehr aber auch nicht. Aber deswegen an jede Piep–Funktion extra einen Application–Pointer durchzureichen weil es ja irgendwann sein koennte dass man mal 10000 ‘Applikationen’ in einem Prozess laufen hat (no comment) … DAS ist ein Verstoss gegen KISS und gegen alle Regeln der Vernunft.

Eine beliebte Loesung fuer diejenigen, die das nachvollziehen koennten, aber trotzdem ohne Klassen nicht leben koennen, sind uebrigens Singletons. Ziemliche Idiotie wenn du mich fragst. Aufrichtiger waere ein Namespace mit ein paar Funktionen drin – insofern hast du, meines Erachtens, alles richtig gemacht.
Seiner Meinung nach gehört so ziemlich alles in eine Klasse (Objektorientiert)
Ja, public static void Main laesst gruessen.

Gruss, Alex


PS: Das hier koennte evtl. fuer dich interessant sein. Darin findest du ein paar alternative Ansaetze um einen Pointer an die WNDPROC weiterzureichen. Falls du immer noch glaubst, dass du das brauchst, versteht sich ;-)
JannBerger
Beiträge: 5
Registriert: 22.12.2010, 11:38
Echter Name: Jann Berger

Re: Muss es denn immer eine Klasse sein?

Beitrag von JannBerger »

Hey danke für die schnellen Antworten.

Werde nun meine WindowProc modifizieren.
Auch zum Thema RAII–Idiom habe ich nun eine interessante Seite gefunden. http://www.gotw.ca/gotw/
Scheint ein echter C++ Guru zu sein :)

Werde nun halt alles nach Lehrers Wunsch programmieren, heimlich aber selber in die Welt von C++ eintauchen :)
Da gibts ja noch unendlich viel zu entdecken.

LG
Benutzeravatar
Schrompf
Moderator
Beiträge: 4996
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: Muss es denn immer eine Klasse sein?

Beitrag von Schrompf »

Mir gefällt Deine Denkweise :-) Neugierig bleiben und trotzdem kurzfristige Einschränkungen (Lehrervorgaben) in Kauf nehmen - ich glaube, wenn Du dran bleibst, wird aus Dir mal ein guter Programmierer. Daumen hoch!
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Jaw
Beiträge: 54
Registriert: 14.07.2004, 01:00
Wohnort: Raum Düsseldorf

Re: Muss es denn immer eine Klasse sein?

Beitrag von Jaw »

Mein Senf: Es ist eine Frage des Standpunkts. Wenn du was für dich so machen würdest, wäre es ganz dein Bier wie elegant oder grob du arbeitest. Wenn es bei dem Projekt vorrangig darum ginge, dass es nur tut was es soll, egal wie, dann wäre die Aufgabe wohl erfüllt, egal wie es innen aus sieht. Wenn man zusätzlich mal unterstellt, dass es nie eine Erweiterung geben wird, ist es auch nicht schlimm, dreckig zu arbeiten.

Großes ABER: Da wir hier von Lehrer, Schule und Kurs reden geht es ja nicht in sich um ein funktional vollständiges Projekt, sondern auch um abstrakte Lerneffekte. Dieses kleine Ding dient da nur als Beispiel zu verdeutlichen, was du gelernt hast und dass du verstanden hast, das gelernte richtig anzuwenden. Dieses Projekt ist nur klein, damit es schaffbar ist, repräsentiert aber ein beliebig großes Projekt. Daher würde ich als Lehrer erwarten, dass auch im kleinen Projekt eine vollständige zur Schau Stellung des Wissens erfolgt und lehrbuchmässige Methoden angewendet werden, auch wenn sie für den konkreten Fall Overkill sind. Aber du sollst auch zeigen, dass du weisst, wie es geht, wenn es drauf an kommt.

Wenn du jetzt einen umgänglichen Lehrer hast, würde ich dem Projekt mindestens eine kleine theoretische Beilage geben, wo drin steht, wie es "idealerweise" hätte sein können, was du dann wie gemacht hättest, und warum du dich dagegen entschieden hast. Um wenigstens in der Theorie zu zeigen, dass du die Lektionen verstanden hast und mit nachvollziehbaren Argumenten bestimmte Entscheidungen getroffen hast. Das würde dann zumindest die Bedingung erfüllen, dass du gelerntes Wissen anwenden kannst.

Wenn du nur ein Projekt lieferst was nicht den Merkmalen entspricht die man versucht dir zu lehren würde ich eine schlechte Bewertung verstehen. Schließlich darf der Lehrer bei der Note nicht spekulieren, dass du es vielleicht gewusst hättest, aber nur nicht gemacht hast, sondern er muss sich an dem orientieren, was du leistest. Und wenn du den Unterrichtsinhalt nicht anwendest, wie soll er das gut benoten?

-JAW
JannBerger
Beiträge: 5
Registriert: 22.12.2010, 11:38
Echter Name: Jann Berger

Re: Muss es denn immer eine Klasse sein?

Beitrag von JannBerger »

@ Jaw

Ich kann deinen Standpunkt absolut verstehen und auch den meines Lehrers. Ich habe mich halt gewundert, wie das andere so machen würden.
So gesehen ist es eigentlich ein gutes Beispiel, was Erfahrung so alles ausmacht. Es fällt mir momentan noch sehr schwer zu entscheiden, welche Lösung nun die angemessene ist.
Ist es nun wirklich eine Klasse, oder reicht eben ein Namensraum usw.

Mit dem Rest des Programms war er auch zufrieden, da ich dort alles schön logisch als Objekte implementieren konnte. Da wäre z.B. das Auto, usw. Nur beim Kern (Fensterverwaltung) war er noch nicht
zufrieden. Nun bin ich am suchen nach einer guten Lösung die ich auch verstehen kann.

Meine aktuelle Vision :D besteht jetzt aus zwei Klassen, dem RenderSystem und dem RenderTarget.
Das RenderSystem kümmert sich um den Adapter, Output und das Renderdevice. Das RenderTarget verwaltet das Fenster, das SwapChain sowie den RenderTargetView/DepthStencilView.
Das würde dann ungefähr so aussehen:

Uncopyable.h

Code: Alles auswählen

#ifndef UNCOPYABLE_H_INCLUDED
#define UNCOPYABLE_H_INCLUDED

namespace Tutorial {

//
// Eine Basisklasse deren ablegeitete Klassen weder kopiert
// noch zugeweiesen werden können.
//
class Uncopyable
{
protected:
    //
    // Konstruktor.
    //
    Uncopyable(void) { }

    //
    // Destruktor.
    //
    virtual ~Uncopyable(void) { }

private:
    //
    // Kopierkonstruktor.
    //
    Uncopyable(const Uncopyable&);

    //
    // Zuweisungsoperator.
    //
    Uncopyable& operator=(const Uncopyable&);
};

} // namespace Tutorial

#endif // UNCOPYABLE_H_INCLUDED
RenderSystem.h/cpp

Code: Alles auswählen

#ifndef RENDER_SYSTEM_H_INCLUDED
#define RENDER_SYSTEM_H_INCLUDED

#include "Common.h"
#include "Uncopyable.h"

namespace Tutorial {

//
// Stellt ein Rendersystem zur Verfügung.
//
class RenderSystem : private Uncopyable
{
public:
    //
    // Konstruktor.
    //
    RenderSystem(void)
    {
    }

    //
    // Destruktor.
    //
    virtual ~RenderSystem(void)
    {
        Destroy();
    }

    //
    // Erstellt das Rendersystem.
    //
    void Create(void)
    {
    }

    //
    // Zerstört das Rendersystem.
    //
    void Destroy(void)
    {
    }

private:
};

} // namespace Tutorial

#endif // RENDER_SYSTEM_H_INCLUDED
RenderTarget.h/cpp

Code: Alles auswählen

#ifndef RENDER_TARGET_H_INCLUDED
#define RENDER_TARGET_H_INCLUDED

#include "Common.h"
#include "Uncopyable.h"
#include "RenderSystem.h"

namespace Tutorial {

//
// Stellt einen Renderbereich zur Verfügung.
//
class RenderTarget : private Uncopyable
{
public:
    //
    // Konstruktor.
    //
    RenderTarget(void) :
        mWindowID(L"Default"),
        mInstanceHandle(NULL),
        mWindowHandle(NULL),
        mWindowShowed(false),
        mRenderSystem(NULL)
    {
    }

    //
    // Destruktor.
    //
    virtual ~RenderTarget(void)
    {
        Destroy();
    }

    //
    // Gibt an ob das Fenster ausgeführt wird.
    // Rückgabewert: Den Fensterstatus.
    //
    bool IsRunning(void) const
    {
        return mWindowShowed;
    }

    //
    // Erstellt den Renderbereich.
    // Parameter instanceHandle: Die Programminstanz.
    // Parameter renderSystem: Das Rendersystem.
    // Parameter windowID: Die eindeutige ID des Fensters.
    // Parameter windowTitle: Der Fenstertitel.
    // Parameter clientWidth: Die Fensterklientbreite.
    // Parameter clientHeight: Die Fensterklienthöhe.
    // Wirf std::exception bei einem Fehler.
    //
    void Create(
        HINSTANCE instanceHandle,
        RenderSystem *renderSystem,
        const std::wstring& windowID,
        const std::wstring& windowTitle, 
        UINT clientWidth, 
        UINT clientHeight)
    {
        if (mWindowHandle)
            return;

        mInstanceHandle = instanceHandle;
        mRenderSystem = renderSystem;
        mWindowID = windowID;

        WNDCLASSEX windowClass;
        windowClass.cbSize = sizeof(windowClass);
        windowClass.cbClsExtra = 0;
        windowClass.cbWndExtra = 0;
        windowClass.hbrBackground = reinterpret_cast<HBRUSH>(::GetStockObject(BLACK_BRUSH));
        windowClass.hCursor = ::LoadCursorW(NULL, IDC_ARROW);
        windowClass.hIcon = ::LoadIconW(NULL, IDI_APPLICATION);
        windowClass.hIconSm = windowClass.hIcon;
        windowClass.hInstance = mInstanceHandle;
        windowClass.lpfnWndProc = WindowProcedure;
        windowClass.lpszClassName = mWindowID.c_str();
        windowClass.lpszMenuName = NULL;
        windowClass.style = CS_DBLCLKS;

        if (!::RegisterClassExW(&windowClass))
        {
            // Die Programminstanz auf Null da die Fensterklasse nicht registriert
            // werden konnte.
            mInstanceHandle = NULL;
            throw std::exception("::RegisterClassExW failed!");
        }

        // Ein Fenster mit einem fixen Fensterrahmen verwenden.
        DWORD windowStyle = WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX;

        // Die Fensterklientgrösse berechnen.
        RECT windowRectangle;
        windowRectangle.left = 0;
        windowRectangle.top = 0;
        windowRectangle.right = static_cast<LONG>(clientWidth);
        windowRectangle.bottom = static_cast<LONG>(clientHeight);

        ::AdjustWindowRect(&windowRectangle, windowStyle, FALSE);

        LONG screenWidth = static_cast<LONG>(::GetSystemMetrics(SM_CXSCREEN));
        LONG screenHeight = static_cast<LONG>(::GetSystemMetrics(SM_CYSCREEN));
        LONG windowWidth = windowRectangle.right - windowRectangle.left;
        LONG windowHeight = windowRectangle.bottom - windowRectangle.top;

        // Das Fenster in der Bildschirmmitte positionieren.
        mWindowHandle = ::CreateWindowW(
            mWindowID.c_str(),
            windowTitle.c_str(),
            windowStyle,
            static_cast<int>((screenWidth - windowWidth) / 2),
            static_cast<int>((screenHeight - windowHeight) / 2),
            static_cast<int>(windowWidth),
            static_cast<int>(windowHeight),
            NULL,
            NULL,
            mInstanceHandle,
            this);

        if (!mWindowHandle)
            throw std::exception("::CreateWindowW failed!");
    }

    //
    // Führt den Renderbereich aus.
    //
    void Run(void)
    {
        if (!mWindowHandle || mWindowShowed)
            return;

        ::ShowWindow(mWindowHandle, SW_SHOWNORMAL);
        ::SetForegroundWindow(mWindowHandle);

        mWindowShowed = true;
    }

    //
    // Zerstört das Fenster.
    //
    void Destroy(void)
    {
        if (mWindowHandle)        
            ::DestroyWindow(mWindowHandle);

        if (mInstanceHandle)
            ::UnregisterClassW(mWindowID.c_str(), mInstanceHandle);

        mWindowID = L"Default";
        mInstanceHandle = NULL;
        mWindowHandle = NULL;
        mWindowShowed = false;
        mRenderSystem = NULL;
    }

private:
    //
    // Verarbeitet die Fensternachrichten.
    // Parameter windowHandle: Das Fenster der entsprechenden Nachricht.
    // Parameter message: Die Nachricht.
    // Parameter wordParameter: Der erste Nachrichtenparameter.
    // Parameter longParameter: Der zweite Nachrichtenparameter.
    // Rückgabewert: Das Resultat der verarbeiteten Nachricht.
    //
    static LRESULT CALLBACK WindowProcedure(
        HWND windowHandle,
        UINT message,
        WPARAM wordParameter,
        LPARAM longParameter)
    {
        if (message == WM_NCCREATE)
        {
            // Das Fenster ermitteln und speichern.
            ::SetWindowLongPtrW(
                windowHandle,
                GWLP_USERDATA, 
                reinterpret_cast<LONG_PTR>(
                reinterpret_cast<LPCREATESTRUCT>(longParameter)->lpCreateParams));               
        }

        // Das Fenster ermitteln.
        RenderTarget* renderTarget = reinterpret_cast<RenderTarget*>(
            ::GetWindowLongPtrW(windowHandle, GWLP_USERDATA));

        switch (message)
        {
        case WM_CLOSE:
            renderTarget->Destroy();
            return 1;
        }

        return ::DefWindowProcW(windowHandle, message, wordParameter, longParameter);
    }
    
private:
    std::wstring mWindowID;
    HINSTANCE mInstanceHandle;
    HWND mWindowHandle;
    bool mWindowShowed;
    RenderSystem* mRenderSystem;
};

} // namespace Tutorial

#endif // RENDER_TARGET_H_INCLUDED
Main.cpp

Code: Alles auswählen

#include "RenderSystem.h"
#include "RenderTarget.h"

namespace Tutorial {

//
// Führt die Anwendung aus.
// Parameter instanceHandle: Die Programminstanz.
// Rückgabewert: false wenn ein Fehler aufgetreten ist.
//
bool Run(HINSTANCE instanceHandle)
{
    std::auto_ptr<RenderSystem> renderSystem(new RenderSystem());
    std::auto_ptr<RenderTarget> renderTarget(new RenderTarget());

    try
    {
        renderSystem->Create();

        renderTarget->Create(
            instanceHandle,
            renderSystem.get(),
            L"RenderTarget",
            L"Test",
            800,
            600);
    }
    catch (const std::exception&)
    {
        // Fehler protokilieren ...
        return false;
    }
                
    renderTarget->Run();

    // Die Hauptschleife starten.
    MSG message;
    message.message = 0;

    while (true)
    {
        // Alle auf dem Nachrichtenstapel liegenden Nachrichten verarbeiten.
        while (::PeekMessageW(&message, NULL, 0, 0, PM_REMOVE))
        {
            ::TranslateMessage(&message);
            ::DispatchMessageW(&message);
        }

        if (!renderTarget->IsRunning())
            break;
    }

    return true;
}

} // namespace Tutorial

//
// Der Haupteinstiegspunkt der Anwendung.
// Parameter instanceHandle: Die Programminstanz.
// Parameter previousInstanceHandle: Die vorherige Programminstanz.
// Parameter commandLine: Die übergebenen Startargumente.
// Parameter windowAppearance: Gibt an wie das Fenster angezeigt werden soll.
// Rückgabewert: Einen Statuscode an das Betriebssystem.
//
int WINAPI wWinMain(
    HINSTANCE instanceHandle,
    HINSTANCE previousInstanceHandle,
    WCHAR* commandLine,
    int windowAppearance)
{
    if (!Tutorial::Run(instanceHandle))
    {
        // Oder Logfile anzeigen? Mal schauen ...
        ::MessageBoxW(
            NULL,
            L"Some error message",
            L"Error Message",
            MB_ICONERROR | MB_SETFOREGROUND);

        return 1;
    }

    return 0;
}
Wie gesagt, mein Lehrer ist nicht böse oder sonstwas, er möchte einfach seine Klasse haben ;)
Und die werde ich ihm auch liefern :D, irgendwie.
Benutzeravatar
exploid
Establishment
Beiträge: 146
Registriert: 21.08.2005, 18:33

Re: Muss es denn immer eine Klasse sein?

Beitrag von exploid »

Als ich deinen Code sah hab ich den meinem alten Informatik Professor gezeigt der mittlerweile auf dem
Friedhof liegt. Der hat sich daraufhin im Grab umgedreht.

OMG du kannst doch nicht alle Errungenschaften von C++ missachten und alles in einem globalen
Namespace schreiben ohne Klassen zu verwenden. Wenn das jeder machen würde. :) :D
All your base are belong to us! Justice
JannBerger
Beiträge: 5
Registriert: 22.12.2010, 11:38
Echter Name: Jann Berger

Re: Muss es denn immer eine Klasse sein?

Beitrag von JannBerger »

Nun zuerst einmal einen guten Start ins neue Jahr euch allen.
Der hat sich daraufhin im Grab umgedreht.
:D Der arme.

Nun, um den guten wider in die ursprüngliche Position zurückzubewegen habe ich mich doch nach den Feierlichkeiten noch mal an meine Fensterumgebung gewagt und eine mittlerweile auch funktionierende 8-) Implementation hervorgebracht. Habe mich aber tief in die Welt von C++ sowie der WinApi stürzen müssen. Doch bei manchen Punkten bin ich noch nicht ganz sicher. Aber zuerst einmal der Code:

Code: Alles auswählen

#include <exception>
#include <memory>
#include <string>
#include <sstream>
#include <Windows.h>
#include <Commctrl.h>
#include "Manifest.h"

#pragma comment(lib, "Comctl32.lib")

namespace Test 
{
    //
    // Beschreibung:
    // Eine von dieser Klasse abgeleitete Klasse kann weder kopiert
    // noch zugewiesen werden.
    //
    class Uncopyable
    {
    protected:
        Uncopyable()
        { 
        }

        virtual ~Uncopyable()
        {
        }

    private:
        Uncopyable(const Uncopyable &);
        Uncopyable &operator=(const Uncopyable &);
    };

    //
    // Beschreibung:
    // Deklaration der Fensterklasse, die dem Nachrichtenbeobachter
    // übgergeben wird.
    //
    class Window;

    //
    // Beschreibung:
    // Kann von einer Klasse implementiert werden und erhält somit die
    // Möglichkeit, sich bei einem Fenster als Beobachter zu registrieren
    // und die spezifischen Nachrichten zu verarbeiten.
    //
    class MessageListener : private Uncopyable
    {
    public:
        //
        // Beschreibung:
        // Die Nachrichtenargumente.
        //
        // Parameter:
        // wordParameter - Das primäre Argument.
        //
        // Parameter:
        // longParameter - Das sekundäre Argument.
        //
        struct Arguments
        { 
            WPARAM wordParameter;
            LPARAM longParameter;
        };
    
    public:
        MessageListener() 
        { 
        }

        virtual ~MessageListener() 
        { 
        }

        //
        // Beschreibung:
        // Wird aufgerufen sobald sich die Fenstergrösse ändert.
        //
        // Parameter:
        // window - Das betreffende Fenster.
        //
        // Parameter:
        // arguments - Die Nachrichtenargumente.
        //
        virtual void SizeMessage(Window &window, const Arguments &arguments)
        {
        }

        //
        // Beschreibung:
        // Wird aufgerufen sobald das Fenster aktiv oder inaktiv wird.
        //
        // Parameter:
        // window - Das betreffende Fenster.
        //
        // Parameter:
        // arguments - Die Nachrichtenargumente.
        //
        virtual void ActivateMessage(Window &window, const Arguments &arguments) 
        { 
        }

        //
        // Beschreibung:
        // Wird aufgerufen sobald eine Taste gedrückt wird.
        //
        // Parameter:
        // window - Das betreffende Fenster.
        //
        // Parameter:
        // arguments - Die Nachrichtenargumente.
        //
        virtual void KeydownMessage(Window &window, const Arguments &arguments) 
        { 
        }

        //
        // Beschreibung:
        // Wird aufgerufen sobald eine Taste freigegeben wird.
        //
        // Parameter:
        // window - Das betreffende Fenster.
        //
        // Parameter:
        // arguments - Die Nachrichtenargumente.
        //
        virtual void KeyupMessage(Window &window, const Arguments &arguments)
        {
        }
    };

    //
    // Beschreibung:
    // Verwaltet die Nachrichtenverarbeitung einer Fensterumgebung und leitet
    // die Nachrichten an das entsprechende Fenster weiter. Wird von der
    // Fensterklasse und anschliessend vom Fenster selber übernommen, welches
    // somit die Nachrichtenprozedur selber implementieren kann.
    //
    class MessageHandler : private Uncopyable
    {
    protected:
        MessageListener *mMessageListener;

    public:
        MessageHandler() : mMessageListener(NULL)
        {
        }

        virtual ~MessageHandler()
        { 
        }

        //
        // Beschreibung:
        // Registriert einen Beobachter für die spezifische Nachrichtenverarbeitung.
        //
        // Parameter:
        // messageListener - Der zu registrierende Beobachter. Wenn Null übergeben
        //                   wird, so wird ein eventuell schon gesetzter abgemelded.
        //
        void RegisterMessageListener(MessageListener *messageListener)
        {
            mMessageListener = messageListener;
        }

    protected:
        //
        // Beschreibung:
        // Wird von der Fensterklasse als Hauptnachrichtenprozedur verwendet.
        // Ermittelt die Fensterklasseninstanz und spechert diese, damit
        // entsprechende Nachrichten an diese weitergeleitet werden können.
        //
        // Parameter:
        // windowHandle - Ein Handle auf das entsprechende Fenster.
        //
        // Parameter:
        // message - Die Nachricht.
        //
        // Parameter:
        // wordParameter - Das primäre Nachrichtenargument.
        //
        // Parameter:
        // longParameter - Das sekundäre Nachrichtenargument.
        //
        // Rückgabewert:
        // Das Resultat der verarbeiteten Nachricht zurück, wobei eine
        // selber verarbeitete Nachricht immer Null zurückgeben sollte.
        //
        static LRESULT CALLBACK StaticMessageProcedure(
            HWND windowHandle, 
            unsigned int message, 
            WPARAM wordParameter, 
            LPARAM longParameter)
        {
            MessageHandler *messageHandler = NULL;

            if (message == WM_NCCREATE)
            {        
                messageHandler =
                    reinterpret_cast<MessageHandler *>(
                    reinterpret_cast<LPCREATESTRUCT>(longParameter)->lpCreateParams);

                ::SetWindowLongPtrW(
                    windowHandle, 
                    GWLP_USERDATA, 
                    reinterpret_cast<LONG_PTR>(messageHandler));
            }        
            else if (message == WM_NCDESTROY)
            {
                ::SetWindowLongPtrW(windowHandle, GWLP_USERDATA, NULL);
            }
            else
            {
                messageHandler = reinterpret_cast<MessageHandler *>(
                    ::GetWindowLongPtrW(windowHandle, GWLP_USERDATA));

                if (messageHandler)
                {
                    bool processMessage = messageHandler->HandleMessage(
                        message,
                        wordParameter, 
                        longParameter);

                    if (!processMessage)
                        return 0;
                }
            }

            return ::DefWindowProcW(windowHandle, message, wordParameter, longParameter);
        }

        //
        // Beschreibung:
        // Wird von der Hauptnachrichtenprozedur aufgerufen und verarbeitet die
        // Nachrichten weiter. Wird später von der Fensterklasse überschrieben
        // um spezifische Nachrichten verarbeiten zu können.
        //
        // Parameter:
        // message - Die Nachricht.
        //
        // Parameter:
        // wordParameter - Das primäre Nachrichtenargument.
        //
        // Parameter:
        // longParameter - Das sekundäre Nachrichtenargument.
        //
        // Rückgabewert:
        // True wenn die Nachricht standardmässig vom System weiterverarbeitet
        // werden soll.
        //
        virtual bool HandleMessage(unsigned int message, WPARAM wordParameter, LPARAM longParameter)
        {
            return true;
        }
    };

    //
    // Beschreibung:
    // Verwaltet die Fensterklasse und wird später von der Fensterklasse
    // übernommen.
    //
    class WindowClass : public MessageHandler
    {
    protected:
        HINSTANCE mInstanceHandle;
        const std::wstring mIdentifier;

    public:
        //
        // Beschreibung:
        // Wird von der Hauptnachrichtenprozedur aufgerufen und verarbeitet die
        // Nachrichten weiter. Wird später von der Fensterklasse überschrieben
        // um spezifische Nachrichten verarbeiten zu können. Verwendet als
        // Nachrichtenprozedur die von der Messagehandlerklasse übernommene
        // Hauptnachrichtenprozedur. Somit erhält das später von dieser Klasse
        // abgeleitete Fenster Zugriff auf die Nachrichtenverarbeitung und kann
        // eine eigene Version implementieren.
        //
        // Parameter:
        // identifier - Ein eindeutiger Identifikationsname. Wenn ein schon
        //              verwendteter übergeben wird, so kann die Fensterklasse
        //              nicht registriert werden.
        //
        // Ausnahme:
        // std::exception - Wird ausgelöst wenn die Fensterklasse nicht
        //                  registriert werden konnte.
        //
        explicit WindowClass(const std::wstring &identifier) :
            mInstanceHandle(::GetModuleHandleW(NULL)), 
            mIdentifier(identifier)
        {
            HGDIOBJ colorHandle = ::GetStockObject(BLACK_BRUSH);
            HCURSOR cursorHandle = ::LoadCursorW(NULL, IDC_ARROW);
            HICON iconHandle = ::LoadIconW(NULL, IDI_APPLICATION);

            WNDCLASSEX windowClass;
            windowClass.cbSize = sizeof(windowClass);
            windowClass.cbClsExtra = 0;
            windowClass.cbWndExtra = 0;
            windowClass.hbrBackground = reinterpret_cast<HBRUSH>(colorHandle);
            windowClass.hCursor = cursorHandle;
            windowClass.hIcon = iconHandle;
            windowClass.hIconSm = iconHandle;
            windowClass.hInstance = mInstanceHandle;
            windowClass.lpfnWndProc = StaticMessageProcedure;
            windowClass.lpszClassName = mIdentifier.c_str();
            windowClass.lpszMenuName = NULL;
            windowClass.style = CS_DBLCLKS;

            if (!::RegisterClassExW(&windowClass))
                throw std::exception("Failed to register window class!");
        }

        //
        // Beschreibung:
        // Meldet die Fensterklasse wieder ab. Somit kann der übergebe
        // Identifikationsname für eine neue Fensterklasse
        // verwendet werden.
        //
        virtual ~WindowClass()
        {
            ::UnregisterClassW(mIdentifier.c_str(), mInstanceHandle);
        }

        //
        // Beschreibung:
        // Gibt den Identifikationsnamen zurück.
        //
        // Rückgabewert:
        // Den Identifikationsnamen.
        //
        const std::wstring &GetIdentifier() const
        {
            return mIdentifier;
        }
    };

    //
    // Beschreibung:
    // Verwaltet ein Standardfenster.
    //
    class Window : public WindowClass
    {
    private:
        HWND mWindowHandle;
        bool mWindowShowed;
        bool mWindowClosed;

    public:        
        //
        // Beschreibung:
        // Erstellt das Fenster und verwendet als Klassenname den von der Fenster-
        // klasse geerbte Identifikationsname. Die Fensterklientgrösse sowie der
        // Fenstertitel können später mittels entsprechenden Methoden selber
        // gesetzt werden.
        //
        // Parameter:
        // identifier - Eine eindeutiger Identifikationsname. Wenn eine
        //              schon verwendtete übergeben wird, so kann die Fensterklasse
        //              nicht registriert werden.
        //
        // Ausnahme:
        // std::exception - Wird ausgelöst wenn das Fenster nicht erstellt
        //                  werden konnte.
        //
        explicit Window(const std::wstring &identifier) : 
            WindowClass(identifier),      
            mWindowHandle(NULL), 
            mWindowShowed(false), 
            mWindowClosed(false)
        {
            mWindowHandle = ::CreateWindowW(
                mIdentifier.c_str(),
                NULL,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                400,
                400,
                NULL,
                NULL,
                mInstanceHandle,
                this);

            if (!mWindowHandle)
                throw std::exception("Failed to create window!");
        }

        //
        // Beschreibung:
        // Zerstört das Fensterhandle und gibt somit alle Ressourcen frei.
        //
        ~Window()
        {
            ::DestroyWindow(mWindowHandle);
        }

        //
        // Beschreibung:
        // Zeigt das Fenster im Vordergrund an und kann nur einmal pro
        // Instanz aufgerufen werden.
        //
        void Show()
        {
            if (!mWindowShowed && !mWindowClosed)
            {
                mWindowShowed = true;
                ::ShowWindow(mWindowHandle, SW_SHOWNORMAL);
                ::SetForegroundWindow(mWindowHandle);
            }
        }

        //
        // Beschreibung:
        // Schliesst das Fenster und kann nur einmal pro Instanz aufgerufen
        // werden.
        //
        void Close()
        {
            if (!mWindowClosed && mWindowShowed)        
            {
                mWindowClosed = true;
                ::ShowWindow(mWindowHandle, SW_HIDE);
            }
        }

        //
        // Beschreibung:
        // Setzt die Fensterklientgrösse und positioniert das Fenster in der
        // Bildschirmmitte.
        //
        // Parameter:
        // clientWidth - Die zu setzende Fensterklientbreite.
        //
        // Parameter:
        // clientHeight - Die zu setzende Fensterklienthöhe.
        //
        void SetClientSize(unsigned int clientWidth, unsigned int clientHeight)
        {
            RECT clientRectangle;
            clientRectangle.left = 0;
            clientRectangle.top = 0;
            clientRectangle.right = static_cast<long>(clientWidth);
            clientRectangle.bottom = static_cast<long>(clientHeight);

            long windowStyle = ::GetWindowLongW(mWindowHandle, GWL_STYLE);
            
            ::AdjustWindowRect(
                &clientRectangle, 
                static_cast<unsigned long>(windowStyle),
                FALSE);

            long windowWidth = clientRectangle.right - clientRectangle.left;
            long windowHeight = clientRectangle.bottom - clientRectangle.top;
            long screenWidth = static_cast<long>(::GetSystemMetrics(SM_CXSCREEN));
            long screenHeight = static_cast<long>(::GetSystemMetrics(SM_CYSCREEN));

            int x = static_cast<int>((screenWidth - windowWidth) / 2);
            int y = static_cast<int>((screenHeight - windowHeight) / 2);
            int width = static_cast<int>(windowWidth);
            int height = static_cast<int>(windowHeight);

            ::MoveWindow(mWindowHandle, x, y, width, height, TRUE);
        }

        //
        // Beschreibung:
        // Setzt den Fenstertitel.
        //
        // Parameter:
        // windowTitle - Die zu setzende Fenstertitel.
        //
        void SetWindowTitle(const std::wstring &windowTitle)
        {
            ::SetWindowTextW(mWindowHandle, windowTitle.c_str());
        }

        //
        // Beschreibung:
        // Gibt das Fensterhandle zurück. Sollte nicht ausserhalb der Fenster-
        // instanz manipuliert werden da es ansonsten zu unvorhersehbaren
        // Fehlern führen kann.
        //
        // Rückgabewert:
        // Das Fensterhandle.
        //
        HWND GetWindowHandle() const
        {
            return mWindowHandle;
        }

        //
        // Beschreibung:
        // Gibt an ob das Fenster angezeigt und nicht geschlossen worden ist.
        //
        // Rückgabewert:
        // True wenn das Fenster am laufen ist.
        //
        bool IsRunning() const
        {
            return mWindowShowed && !mWindowClosed;
        }

    private:
        //
        // Beschreibung:
        // Die von der Messagehandlerklasse geerbte Nachrichtemethode. Implementiert
        // eine eigene, speziell für das Fenster zugeschnittene Version und leitet
        // ensprechende Nachrichten an einen eventuell registrierten Beobachter weiter.
        //
        // Parameter:
        // message - Die Nachricht.
        //
        // Parameter:
        // wordParameter - Das primäre Nachrichtenargument.
        //
        // Parameter:
        // longParameter - Das sekundäre Nachrichtenargument.
        //
        // Rückgabewert:
        // True wenn die Nachricht standardmässig vom System weiterverarbeitet
        // werden soll.
        //
        bool HandleMessage(unsigned int message, WPARAM wordParameter, LPARAM longParameter)
        {       
            if (message == WM_CLOSE)
            {
                Close();
                return false;
            }

            MessageListener::Arguments arguments;                
            arguments.wordParameter = wordParameter;
            arguments.longParameter = longParameter;

            if (mMessageListener)
            {
                switch (message)
                {
                case WM_SIZE:
                    mMessageListener->SizeMessage(*this, arguments);
                    break;

                case WM_ACTIVATE:
                    mMessageListener->ActivateMessage(*this, arguments);
                    break;

                case WM_KEYDOWN:
                    mMessageListener->KeydownMessage(*this, arguments);
                    break;

                case WM_KEYUP:
                    mMessageListener->KeyupMessage(*this, arguments);
                    break;
                }
            }

            return true;
        }
    };

    //
    // Beschreibung:
    // Die Hauptanwendung agiert als Nachrichtenbeobachter und kann bei
    // einem Fenster als solcher Registriert werden.
    //
    class Application : public MessageListener
    {
    public:
        //
        // Beschreibung:
        // Der Haupteinstiegspunkt der Anwendung.
        //
        static void Main()
        {
            try
            {
                Application application;
                application.Run();
            }
            catch (const std::exception &exception)
            {
                ::MessageBoxA(NULL, exception.what(), "Error", MB_ICONERROR);
            }
        }

    private:
        Application() 
        { 
        }

        ~Application()
        {
        }
    
        //
        // Beschreibung:
        // Führt die Anwendung aus und implementiert eine Umgebung mit
        // mehreren Fensters zu Testzwecken. Allen Fenster wird die
        // gleiche Beobachterinstanz, nähmlich die Anwendung selber
        // übergeben. Die Anwendung wird solange ausgeführt, bis alle
        // Fenster geschlossen worden sind.
        //
        void Run()
        {
            std::auto_ptr<Window> window1(new Window(L"Window 1"));
            std::auto_ptr<Window> window2(new Window(L"Window 2"));
            std::auto_ptr<Window> window3(new Window(L"Window 3"));

            window1->RegisterMessageListener(this);
            window1->SetClientSize(1024, 768);
            window1->Show();

            window2->RegisterMessageListener(this);
            window2->SetClientSize(800, 600);
            window2->Show();

            window3->RegisterMessageListener(this);
            window3->SetClientSize(640, 480);
            window3->Show();

            while (window1->IsRunning() || window2->IsRunning() || window3->IsRunning())
            {
                PeekMessages();            
            }
        }

        //
        // Beschreibung:
        // Verarbeitet alle auf dem Nachrichtenstapel liegenden Nachrichten
        // und entfernt diese anschliessend.
        //
        void PeekMessages()
        {
            MSG message;

            while (::PeekMessageW(&message, NULL, 0, 0, PM_REMOVE))
            {
                ::TranslateMessage(&message);
                ::DispatchMessageW(&message);
            }
        }

        //
        // Beschreibung:
        // Verarbeitet eine Fenstergrössenmanipulation und setzt die
        // neue Fensterklientgrösse als neuen Fenstertitel des entsprechenden
        // Fensters.
        //
        // Parameter:
        // window - Das betreffende Fenster.
        //
        // Parameter:
        // arguments - Die Nachrichtenargumente.
        //
        void SizeMessage(Window &window, const Arguments &arguments) 
        {
            std::wstringstream clientSize;
            clientSize
                << LOWORD(arguments.longParameter)
                << L" x "
                << HIWORD(arguments.longParameter);

            window.SetWindowTitle(clientSize.str());
        }

        //
        // Beschreibung:
        // Verarbeitet eine Fensterstatusmanipulation und setzt den neuen
        // Status als neuen Fenstertitel des entsprechenden Fensters.
        //
        // Parameter:
        // window - Das betreffende Fenster.
        //
        // Parameter:
        // arguments - Die Nachrichtenargumente.
        //
        void ActivateMessage(Window &window, const Arguments &arguments) 
        { 
            if (arguments.wordParameter)
                window.SetWindowTitle(L"Active");
            else
                window.SetWindowTitle(L"Inactive");
        }

        //
        // Beschreibung:
        // Verarbeitet einen Tastendruck eines bestimmten Fensters.
        //
        // Parameter:
        // window - Das betreffende Fenster.
        //
        // Parameter:
        // arguments - Die Nachrichtenargumente.
        //
        void KeydownMessage(Window &window, const Arguments &arguments)
        {
            switch (arguments.wordParameter)
            {
            case VK_ESCAPE:
                window.Close();
                break;
            }
        }
    
        //
        // Beschreibung:
        // Verarbeitet eine Tastenfreigabe eines bestimmten Fensters.
        //
        // Parameter:
        // window - Das betreffende Fenster.
        //
        // Parameter:
        // arguments - Die Nachrichtenargumente.
        //
        void KeyupMessage(Window &window, const Arguments &arguments)
        {
        }
    };
}

//
// Beschreibung:
// Der Haupteinstiegspunkt des Programms. Initialisiert die
// visuellen Fensterkomponenten und ruft die Hauptanwendung auf.
//
// Parameter:
// instanceHandle - Wird nicht verwendet.
//
// Parameter:
// previousInstanceHandle - Wird nicht verwendet.
//
// Parameter:
// commandLine - Wird nicht verwendet.
//
// Parameter:
// windowAppearance - Wird nicht verwendet.
//
// Rückgabewert:
// Einen Statuscode and das aufrufende System wobei jeder ungleich Null
// einem Fehlercode entspricht.
//
int WINAPI wWinMain(
    HINSTANCE instanceHandle, 
    HINSTANCE previousInstanceHandle, 
    wchar_t *commandLine, 
    int windowAppearance)
{
    INITCOMMONCONTROLSEX commonControls;
    commonControls.dwSize = sizeof(commonControls);
    commonControls.dwICC = ICC_STANDARD_CLASSES;

    ::InitCommonControlsEx(&commonControls);

    Test::Application::Main();
    return 0;
}
Es kommen noch ein paar weiter Methoden hinzu z.B. für die Mausverwaltung usw.
Nun bin ich mir aber nicht ganz sicher, ob ich das C++ Klassenkonzept auch richtig verstanden habe. Wenn jetzt z.B. die Fensterklasse erfolgreich erstellt werden würde, das Fenster aber nicht, so würde diese eine Ausnahme auslösen. Da jetzt aber das Fensterobjekt ja nie bestanden hat, so wird auch kein Destruktor aufgerufen. So weit so klar. Nur wie sieht das mit der Fensterklasse aus?
Diese wurde ja erfolgreich erstellt, wird deren Destruktor dann trotzdem aufgerufen?
Zumindest bei meiner Visual Studio 2010 Express Edition ist dies der Fall. Doch gehört dieses Verhalten auch zum Standard der Sprache?

Besten Dank schon mal im Voraus
Benutzeravatar
Jonathan
Establishment
Beiträge: 2516
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Muss es denn immer eine Klasse sein?

Beitrag von Jonathan »

Na, bei jeder Funktion, die verlassen wird, werden alle Variablen die am Stack waren zerstört, genau wie wenn sie normal verlassen worden wäre. Und imemr wenn ein Objekt zerstört wird, wird sein DTor aufgerufen.
Was alles zerstört (bzw. aufgeräumt wird) hängt also entscheidend davon ab, wo die Exception gefangen wird.
Und da mit new allokierte Objete nicht am Stack sind, werden diese auch nicht gelöscht. Hier muss man also bei Exceptions aufpassen und z.B. smart Pointer verwenden, die dann am stack liegen und in ihrem Dtor den Speicher freigeben.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8305
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Muss es denn immer eine Klasse sein?

Beitrag von Krishty »

JannBerger hat geschrieben:Wenn jetzt z.B. die Fensterklasse erfolgreich erstellt werden würde, das Fenster aber nicht, so würde diese eine Ausnahme auslösen. Da jetzt aber das Fensterobjekt ja nie bestanden hat, so wird auch kein Destruktor aufgerufen. So weit so klar. Nur wie sieht das mit der Fensterklasse aus?
Diese wurde ja erfolgreich erstellt, wird deren Destruktor dann trotzdem aufgerufen?
Zumindest bei meiner Visual Studio 2010 Express Edition ist dies der Fall. Doch gehört dieses Verhalten auch zum Standard der Sprache?
Ja, der Standard garantiert das. Das hier mal zur Veranschaulichung:

Code: Alles auswählen

class Base {
protected:
    string myName;

    explicit
    Base(string const & itsName)
        : myName(itsName) // (1)
    {
        // (2)
    }

    virtual void overridden() { … };

    virtual
    ~Base() { }
};

class Derived : public Base {
    auto_ptr<int> myAutoPointer;
    int * myBadPointer;
public:

    Derived()
        : Base("blubb")
        , myAutoPointer(new int) // (3)
        , myBadPointer(new int) // (4)
    {
        // (5)
        string tempString("foo");
        // (6)
        overridden(); // Bemerkung 1
    }

    virtual void overridden() { … }

    void run() {
        overridden(); // Bemerkung 2
    };

    ~Derived() {
        // 7
        delete myBadPointer;
        // ruft bei Abschluss auch den D’tor von Base auf
    }
};

int main() {
    Derived instance;
    instance.run(); // 8
    return 0;
}
Wichtig ist nun, zu wissen, dass der C++-Standard ein Objekt als initialisiert definiert, sobald einer seiner Konstruktoren vollendet wurde. Weiterhin ist zu wissen, dass ein Konstruktor erst alle Basisklassen initialisiert und dann die Member (in Reihenfolge ihrer Deklaration), ganz egal, in welcher Reihenfolge man die Initialisierungsliste schreibt!
Mal eine Aufzählung, was passieren würde, wenn an den mit Zahlen markierten Stellen Ausnahmen geworfen würden:
  • myName schmeißt während der Erzeugung eine Ausnahme. Es ist noch nicht initialisiert, also muss auch nichts freigegeben werden. Base gilt aber noch nicht als initialisiert, weil ihr K’tor noch im Gange ist – darum würde hier auch nicht ihr D’tor aufgerufen. Die Ausnahme wird einfach nach oben „durchgereicht“.
  • myName ist hier bereits initialisiert worden, also wird es auch wieder freigegeben.
  • Jetzt wird es interessanter. Die Basisklasse Base ist initialisiert worden. Also wird sie auch wieder (mit D’tor-Aufruf) freigegeben, wenn nun eine Exception fliegt. Derived aber nicht, da noch nicht fertiginitialisiert. myAutoPointer ist noch nicht initialisiert (bei seiner Initialisierung flog ja die Ausnahme), also muss es auch nicht freigegeben werden.
  • myAutoPointer ist ebenfalls vollständig initialisiert. Hier würden also myAutoPointer, die Basisklasse Base sowie alle ihre Attribute freigegeben.
  • Hier kommt nun zum Vorschein, warum Zeiger als Objektbesitzer wie Schwänze im Bordell sind – nur gut, wenn sie geschützt werden: Fliegt hier eine Exception, wird zwar myBadPointer freigegeben, aber nicht der Speicher, auf den er zeigt (denn die Instanz von Derived gilt nicht als initialisiert und damit wird auch ihr Destruktor, der den Zeiger freigibt, nicht aufgerufen). Ab hier hätten wir also ein Speicherleck.
  • Lokale Variablen werden eh nach ihrer Erzeugung wieder freigegeben – ganz gleich, ob durch erfolgreichen Abschluss der Funktion oder durch Wurf einer Ausnahme. myBadPointer ist und bleibt das einzige Speicherleck.
  • Destruktoren sollten keine Ausnahmen werfen. In diesem Fall wäre es harmlos (abgesehen davon, dass der D’tor nicht vollendet würde und damit auch der Speicher von myBadPointer nicht freigegeben würde) – aber du musst bedenken, dass D’toren auch aufgerufen werden, wenn bereits eine Exception in der Luft ist und die Objekte zwischen Wurf und Fang aufgeräumt werden. Schmeißt du in diesem Fall eine Exception, kann C++ nicht mehr garantieren dass beide Fehler verarbeitet werden und beendet dein Programm auf der Stelle.
  • Hier gilt die Instanz als vollständig initialisiert und wenn hier eine Exception flöge würde der D’tor von Derived aufgerufen. Der würde myBadPointer freigeben und wir hätten kein Speicherleck.
Diese ganze Chose hat zum Ziel, dass D’toren nur völlig initialisierte Objekte zum Aufräumen vorfinden und man keinen „Schwebezustand“ einführen muss, in dem man aus einem halb initialisierten Objekt die initialisierten Attribute rauspicken und freigeben muss. (Das wurde durch C++0x’ Move Semantics ruiniert, aber das ist eine ganz andere Geschichte.)

Bemerkungen: Dass C++ ein Objekt erst als initialisiert ansieht, wenn sein Konstruktor vollendet ist, hat eine auf den ersten Blick kontraintuitive Nebenwirkung: Die virtuellen Funktionen der abgeleiteten Klasse stehen ebenfalls erst zur Verfügung, wenn einer ihrer K’toren vollendet wurde. Das bedeutet, dass der erste Aufruf overridden(); vom K’tor aus nicht die überschriebene Methode aufrufen würde, sondern die Methode der Basisklasse! Das ist eigentlich logisch – denn so lange der K’tor nicht vollendet wurde, gilt das Objekt höchstens als Instanz seiner Basisklassen, nicht als Instanz seines endgültigen Typs. (U.A. darum sollten K’toren keine oder möglichst nur für diesen Zweck geschriebene Methoden aufrufen. C++ strebt K’toren an, die in der Initialisierungsliste Parameter an ihre Basisklassen und Attribute weiterdirigieren und sonst möglichst keine Arbeit verrichten. Dass das in der Praxis nur selten und bei Benutzung von z.B. der WinAPI unmöglich zu erreichen ist, ist klar … aber wenn man „höhere“ Klassen programmiert und bis dahin alles richtig gemacht hat, werden die Konstruktoren tatsächlich gespenstisch leer.)
Ein Aufruf overridden();, der nach dem K’tor erfolgt, ruft hingegen (wie erwartet) die überschriebene Methode auf.

RAII ist atemberaubend. So kompliziert die Regeln nämlich anmuten, am Ende reduziert sich alles zu: Ist das Objekt initialisiert, wird es freigegeben. Wenn nicht, nicht. Wenn du also darauf achtest, dass du deine Objekte immer als initialisiert ansehen kannst, wenn sie auch die Sprache als initialisiert ansieht (nämlich mit Abschluss des K’tors), brauchst du dir garkeine Gedanken mehr über Ausnahmen oder Ressourceverwaltung machen – etwas, was in anderen Sprachen fehlt (und dann mit finally vom Programmierer übernommen werden muss). Du schreibst dein Programm einfach so, als würden Fehler nie geschehen … und falls mal einer kommt, bürgt die Sprache dafür, dass du dir zwischen Auftreten des Fehlers und der Stelle, die du für Fehlerbehandlung vorgesehen hast (den catch-Block) über nichts einen Kopf machen musst.

Gruß, Ky
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
JannBerger
Beiträge: 5
Registriert: 22.12.2010, 11:38
Echter Name: Jann Berger

Re: Muss es denn immer eine Klasse sein?

Beitrag von JannBerger »

Ah, vielen Dank für die Informationen.

Nun zuerst hatte ich eine Initialize() Methode implementiert. Danach musste ich im Destruktor prüfen, ob auch wirklich Initialize() aufgerufen worden ist und ob die entsprechende Ressource freigegeben werden muss.
Ich fand es aber eleganter, dass wenn ich ein Objekt erstellt habe, es auch erstellt ist. An die Nebenwirkung habe ich aber so nicht gedacht :oops:

Unteranderem bewirkt ja der Aufruf von ::CreateWindowW schon entsprechende WM_... Nachrichten. Und diese kommen aus dem Konstruktor. OK, das werde ich ändern.

Bin da noch über einen interessanten Artikel zum Thema std::auto_ptr gestossen. http://www.gotw.ca/gotw/056.htm
C++ ist extrem spannend, da gibts ja immer was zu lernen :)

LG
Benutzeravatar
Krishty
Establishment
Beiträge: 8305
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Muss es denn immer eine Klasse sein?

Beitrag von Krishty »

JannBerger hat geschrieben:Unteranderem bewirkt ja der Aufruf von ::CreateWindowW schon entsprechende WM_... Nachrichten. Und diese kommen aus dem Konstruktor. OK, das werde ich ändern.
Hmmm. Ich könnte mich irren, aber soweit ich weiß, werden die Nachrichten ja im Betriebssystem vorgehalten und erst verarbeitet, wenn du GetMessage() o.Ä. aufrufst – und so lange du das nicht im K’tor machst, ist alles in Butter. Meine WinAPI-Erfahrung hält sich aber in Grenzen.

Versuch da nicht aufs Verderben, die perfekte Kapselung reinzubringen. Das ist mit der WinAPI unmöglich; da sind schon Größere dran gescheitert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: Muss es denn immer eine Klasse sein?

Beitrag von Aramis »

Versuch da nicht aufs Verderben, die perfekte Kapselung reinzubringen. Das ist mit der WinAPI unmöglich; da sind schon Größere dran gescheitert.
Richtig! Wie ich oben schrieb, ist das WinAPI nicht mit C++ und seinen Paradigmen zur Resourcenverwaltung und -Kapselung im Hinterkopf entstanden. Es in eine absolut C++oide Struktur zu pressen wird und kann nicht klappen. Versuch lieber, moeglichst schnell davon wegzukommen. In vielen Faellen haelt sich der WinAPI-Code mengenmaessig ja sehr in Grenzen im Vergleich zur eigentlichen Programmlogik, die du ja sauber wegkapseln kannst -
Benutzeravatar
Biolunar
Establishment
Beiträge: 154
Registriert: 27.06.2005, 17:42
Alter Benutzername: dLoB

Re: Muss es denn immer eine Klasse sein?

Beitrag von Biolunar »

Krishty hat geschrieben:
JannBerger hat geschrieben:Unteranderem bewirkt ja der Aufruf von ::CreateWindowW schon entsprechende WM_... Nachrichten. Und diese kommen aus dem Konstruktor. OK, das werde ich ändern.
Hmmm. Ich könnte mich irren, aber soweit ich weiß, werden die Nachrichten ja im Betriebssystem vorgehalten und erst verarbeitet, wenn du GetMessage() o.Ä. aufrufst – und so lange du das nicht im K’tor machst, ist alles in Butter. Meine WinAPI-Erfahrung hält sich aber in Grenzen.
Ne, wird wirklich schon bei CreateWindow geschickt.
Antworten