2016-07-27 15 views
1

Ich entwerfe eine Schnittstelle, um die Aufgaben der Verwaltung von Direct3D, Direct2D, DXGI und den zugeordneten Win32API-Aufrufen zu abstrahieren.Verwenden eines Namespaces anstelle einer Klasse

Behalten Sie alles in einem Namespace oder Refactor, um eine Klasse zu verwenden?

WindowsApp.h

#pragma once 
#include <Windows.h> 

namespace WindowsApp 
{ 
    bool Initialize(HINSTANCE instanceHandle); 
} 

WindowsApp.cpp

#include "WindowsApp.h" 

namespace WindowsApp 
{ 
    namespace 
    { 
     HWND ghMainWnd = 0; 
    } 

    LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 
    { 
     switch (msg) 
     { 
     case WM_DESTROY: 
     { 
      PostQuitMessage(0); 
      return 0; 
     } 
     break; 

     default: 
     { 
      return DefWindowProc(hWnd, msg, wParam, lParam); 
     } 
     } 
    } 

    bool Initialize(HINSTANCE instanceHandle) 
    { 
     WNDCLASS wc; 
     wc.style = CS_HREDRAW | CS_VREDRAW; 
     wc.lpfnWndProc = WndProc; 
     wc.cbClsExtra = 0; 
     wc.cbWndExtra = 0; 
     wc.hInstance = instanceHandle; 
     wc.hIcon = LoadIcon(0, IDI_APPLICATION); 
     wc.hCursor = LoadCursor(0, IDC_ARROW); 
     wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); 
     wc.lpszMenuName = 0; 
     wc.lpszClassName = L"BasicWndClass"; 

     if (!RegisterClass(&wc)) 
     { 
      MessageBox(0, L"RegisterClass FAILED", 0, 0); 
      return false; 
     } 

     ghMainWnd = CreateWindow(
      L"BasicWndClass", 
      L"Win32Basic", 
      WS_OVERLAPPEDWINDOW, 
      CW_USEDEFAULT, 
      CW_USEDEFAULT, 
      CW_USEDEFAULT, 
      CW_USEDEFAULT, 
      0, 
      0, 
      instanceHandle, 
      0); 

     if (ghMainWnd == 0) 
     { 
      MessageBox(0, L"CreateWindow FAILED", 0, 0); 
     } 

     ShowWindow(ghMainWnd, 1); 
     UpdateWindow(ghMainWnd); 

     return true; 
    } 
} 

Main.cpp

#include "WindowsApp.h" 

int Run() 
{ 
    MSG msg = { 0 }; 

    BOOL bRet = 1; 
    while ((bRet = GetMessage(&msg, 0, 0, 0)) != 0) 
    { 
     if (bRet == -1) 
     { 
      MessageBox(0, L"GetMessage FAILED", L"Error", MB_OK); 
      break; 
     } 
     else 
     { 
      TranslateMessage(&msg); 
      DispatchMessage(&msg); 
     } 
    } 
    // Deinitialize Here 
    return (int)msg.wParam; 
} 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nShowCmd) 
{ 
    if (!WindowsApp::Initialize(hInstance)) { return 0; } 
    return Run(); 
} 

eine namesp Verwendung Mit ass kann ich Implementierungsdetails im verschachtelten unbenannten Namespace ausblenden. Eine Klasse lässt mich nicht Dinge verstecken, aber ich kann sie innerhalb der privaten Abteilung unzugänglich machen, das ist gut genug, nehme ich an.

Die Verwendung einer Klasse birgt das Risiko, dass Benutzer versuchen, mehrere Objekte zu instanziieren, was dazu führen würde, dass die Anwendung zweimal von DirectX initialisiert wird. Namespaces vermeiden dieses Problem, aber sie stellen einen Leistungsminderungsfaktor dar. Ich muss die Initialized Variable bei jedem Funktionsaufruf überprüfen. Ich mag das wirklich nicht.

Schließlich erfordert die Verwendung einer Klasse, dass der Benutzer das instanziierte Objekt überall in der Anwendung umgeht, wo auch immer die zugrunde liegenden Methoden benötigt werden. Das ist wirklich enttäuschend, da der Namespace-Ansatz mir Zugriff gibt, wenn ich mich in einer Datei befinde, die einen #include in der Namespace-Header-Datei hat. Das gefällt mir wirklich gut.

Der Namespace-Ansatz scheint der beste Weg zu sein, aber etwas stimmt nicht ganz mit mir in der Art überein, wie Variablen innerhalb des verschachtelten unbenannten Namespaces behandelt werden. Ist das eine gute Übung? Mein Bauch sagt mir nein! Nein! Nein!

Also ich denke, meine Frage wäre: Ist das ein geeigneter Anwendungsfall für Namespaces?

Zur Klarstellung: - vorwärts Erklärungen zu allen Funktionen sind innerhalb WindowsApp.h - ich die unbenannte Namespace in WindowsApp.cpp zusammen mit den Funktionsdefinitionen definiert haben Durch die Verwendung dieser Funktionen aufzurufen manipuliert die Variablen innerhalb der unbenannter Namespace Ist das eine schlechte Verwendung von Namespaces oder sollte es anders gemacht werden? Wenn Sie einfach die Header-Datei in eine andere .cpp-Datei einfügen, erhalten Sie Zugriff auf die Funktionen und damit auf die zugrunde liegenden Daten. Das ist sehr ansprechend. Mein Bauchgefühl sagt mir, dass es eine Art von Leistungsstrafe geben wird, die sich aus der Strukturierung ergeben wird.

+1

klingt für mich, als ob Sie ein Singleton-Muster suchen – vu1p3n0x

+0

Auf den ersten Blick tut es. Dieses Namespacemuster klingt wie der bessere Ansatz, dem ich laut bin, Variablen im verschachtelten unbenannten Namespace zu deklarieren und dann über die Funktionen, die im übergeordneten Namespace definiert sind, darauf zuzugreifen. Nicht ein instanziiertes Klassenobjekt um die Anwendung zu übergeben, nur um Zugriff auf die Member-Funktionen zu bekommen, scheint überflüssig, wenn man die Datei, die alle Forward-Deklarationen für die Namespace-Funktionen enthält, einfach "einschließen" kann. – KKlouzal

+1

Sie müssen nicht unbedingt überall eine Instanz übergeben, die einzelne Instanz konnte statisch abgerufen werden 'WindowsAppClass :: getInstance()' – vu1p3n0x

Antwort

2

[EDIT: Entfernen Sachen über unbenannte Namensräume beginnen eindeutig TUs nun der Code in der Frage geklärt ist.]

In C++ wir neigen dazu, ein Wrapper für einige Daten ein class zu denken, als die pflegt eine Invariante (im Gegensatz zu einer struct, die für eine Reihe von Daten ohne Invariante verwendet wird). Der Konstruktor richtet die Invariante ein, der Destruktor zerreißt sie, und die Elementfunktionen sorgen dafür, dass sie beibehalten wird. Hier, wenn ich richtig verstanden habe, scheint es, dass Ihre Invariante ist, dass Initialized() aufgerufen werden muss, bevor Sie eine andere API-Funktion verwenden.

Es gibt eine andere Alternative, die sogenannte "magische Statik" zu verwenden, auch bekannt als "Meyers Singletons". Tun Sie etwas wie folgt aus:

// In WindowsApp.cpp 

namespace { 

class WindowsAppImpl { 
public: 
    WindowsAppImpl() 
    { 
     // Do initialization stuff here 
    } 

    ~WindowsAppImpl() 
    { 
     // Do teardown stuff if necessary 
    } 

    // Example function 
    int getMagicNumber() 
    { 
     return 3; 
    } 
}; 

WindowsAppImpl& GetInstance() { 
    static WindowsAppImpl instance{}; 
    return instance; 
} 

} // end private namespace 

// Public function declared in WindowApp.h 
int GetMagicNumber() { 
    // Get the singleton instance 
    WindowsAppImpl& instance = GetInstance(); 

    // Call member function 
    return instance.getMagicNumber(); 
} 

Dieser Ansatz fügt eine Funktion, die einen Verweis auf die Singleton WindowsAppImpl Instanz zurückgibt. Der Compiler garantiert, dass diese Instanz genau einmal aufgebaut wird, beim ersten Aufruf von GetInstance(). (Es wird auch der Destruktor für WindowsAppImpl nach main() ausgeführt, was in diesem Fall wahrscheinlich nicht wichtig ist, aber in einigen Fällen nützlich sein kann.) Der Vorteil dieses Ansatzes ist, dass innerhalb GetMagicNumber() Sie sicher sein können, die Initialisierungsroutinen ausgeführt worden sind , ohne dass der Benutzer eine eigene Instanz von WindowsAppContext passieren muss.

+0

Ich habe den unbenannten Namespace in WindowsApp.cpp zusammen mit den Funktionsdefinitionen - forward declarations definiert Alle Funktionen sind innerhalb von WindowsApp.h - Durch den Aufruf dieser Funktionen manipuliert die Variablen im unbenannten Namespace. Ist das eine schlechte Verwendung von Namespaces oder sollte es anders gemacht werden? Wenn Sie einfach die Header-Datei in eine andere .cpp-Datei einfügen, erhalten Sie Zugriff auf die Funktionen und damit auf die zugrunde liegenden Daten. Das ist sehr ansprechend. Mein Bauchgefühl sagt mir, dass es eine Art von Leistungsstrafe geben wird, die sich aus der Strukturierung ergeben wird. – KKlouzal

+0

'in C++ neigen wir dazu, eine Klasse als einen Wrapper für einige Daten zu betrachten, der eine Invariante aufrechterhält (im Gegensatz zu einer Struktur, die für eine Menge von Daten ohne Invariante verwendet wird) ? Ich werde das '* wir * tendieren zu' ablehnen. – dxiv

+0

@dxiv Es gibt fast keinen Unterschied zwischen der Bedeutung der Schlüsselwörter 'class' und' struct' in der Sprache (Sie könnten jede Verwendung von 'class' durch' struct' im durchschnittlichen C++ Programm ersetzen und es würde immer noch funktionieren) die Invariante vs keine Invariante Unterscheidung ist eher wie eine nützliche Konvention als etwas, das von der Sprache unbedingt benötigt wird. Wie jede Konvention wird sie jedoch nicht von allen eingehalten und es ist leicht, Code zu finden, der dies nicht tut. Deshalb habe ich gesagt "wir neigen dazu" ... "struct" verwendet zu sehen ist ein starkes Anzeichen, dass es keine Invariante gibt, aber (leider) keine Garantie. –