2017-11-17 4 views
1

Ich schreibe ein Programm, das die Option hat, die Ausgabe eines Algorithmus zu visualisieren, an dem ich arbeite - dies geschieht durch Ändern einer in einer Headerdatei definierten const bool VISUALIZE_OUTPUT Variable. In der Hauptdatei, möchte ich diese Art von Muster haben:Bedingte Erstellung eines Objekts in C++

if(VISUALIZE_OUTPUT) { 
    VisualizerObject vis_object; 
} 
... 
if(VISUALIZE_OUTPUT) { 
    vis_object.initscene(objects_here); 
} 
... 
if(VISUALIZE_OUTPUT) { 
    vis_object.drawScene(objects_here); 
} 

Dies wird jedoch eindeutig nicht kompilieren, da vis_object den Gültigkeitsbereich verlässt. Ich möchte das Objekt nicht vor der Bedingung deklarieren, da es ein großes Objekt ist und es für mehrere Punkte im Code verfügbar sein muss (ich kann nicht einfach eine bedingte Anweisung haben, bei der alles erledigt ist).

Was ist der bevorzugte Weg, dies zu tun?

  • Deklarieren Sie das Objekt auf dem Heap und verweisen Sie darauf mithilfe eines Zeigers (oder unique_ptr)?
  • Deklarieren Sie das Objekt auf dem Heap und stellen Sie einen Verweis darauf da es nie ändern wird?
  • Eine andere Alternative?
  • +2

    Haben Sie ** ein Leistungsproblem gemessen, wenn Sie nur 'VisualizerObject vis_object;' im äußersten Bereich haben, der es sein muss? – Caleth

    +0

    Ich denke, Ihre gewünschte "Art von Muster" ist fehlerhaft - im Wesentlichen suchen Sie eine Möglichkeit, Dinge in einer Weise zu tun, die den Umfang nicht respektiert. Daher wäre eine praktikable Option, 'VISUALIZE_OUTPUT' zu einem Präprozessor-Makro zu machen und sowohl die Definition als auch die Verwendung von' vis_object' in '#ifdef VISUALISE_OUTPUT' /' # endif' zu verpacken. Dies funktioniert, da der Präprozessor den Gültigkeitsbereich nicht berücksichtigt. Bedenken Sie jedoch, dass die Verwendung von Preprozessor-Tricks in C++ aus guten Gründen dringend vermieden wird. – Peter

    Antwort

    1

    Eine Referenz ist hier nicht verwendbar, da sie bei der Deklaration auf ein bereits existierendes Objekt referenzieren sollte und in einem Bereich liegt, in dem alle Ihre if(VISUALIZE_OUTPUT) enthalten sind. Lange Rede, kurzer Sinn, das Objekt muss unbedingt geschaffen werden.

    Also IMHO eine einfache Möglichkeit wäre, es auf dem Haufen zu erstellen und es durch einen Zeiger zu verwenden - vergessen Sie nicht, tun delete es, wenn Sie fertig sind. Der gute Punkt ist, dass der Zeiger auf nullptr initialisiert werden könnte, und so könnte es bedingungslos gelöscht werden.

    Aber ich denke, dass der beste Weg wäre, alles in einem Objekt in den höchsten Umfang eingekapselt zu kapseln. Dieses Objekt würde dann Methoden enthalten, um das tatsächliche vis_object zu erstellen, intern zu verwenden und schließlich zu zerstören. Auf diese Weise wird, wenn Sie es nicht benötigen, nichts instanziiert, aber die Hauptprozedur wird nicht mit roher Zeigerverarbeitung überladen.

    0

    würde ich Null_object_pattern verwenden:

    struct IVisualizerObject 
    { 
        virtual ~IVisualizerObject() = default; 
        virtual void initscene(Object&) = 0; 
        virtual void drawScene(Object&) = 0; 
        // ... 
    }; 
    
    struct NullVisualizerObject : IVisualizerObject 
    { 
        void initscene(Object&) override { /* Empty */ } 
        void drawScene(Object&) override { /* Empty */} 
        // ... 
    }; 
    
    struct VisualizerObject : IVisualizerObject 
    { 
        void initscene(Object& o) override { /*Implementation*/} 
        void drawScene(Object& o) override { /*Implementation*/} 
        // ... 
    }; 
    

    Und schließlich:

    std::unique_ptr<IVisualizerObject> vis_object; 
    if (VISUALIZE_OUTPUT) { 
        vis_object = std::make_unique<VisualizerObject>(); 
    } else { 
        vis_object = std::make_unique<NullVisualizer>(); 
    } 
    
    // ... 
    vis_object->initscene(objects_here); 
    
    //... 
    
    vis_object->drawScene(objects_here); 
    
    0

    Ich werde ein paar Optionen. Alle haben Vor- und Nachteile. Wenn es NICHT möglich ist, VisualizerObject zu ändern, wie ich in Kommentaren bemerkte, könnte der Effekt durch Verwendung des Präprozessors erreicht werden, da der Präprozessor den Gültigkeitsbereich nicht respektiert und die Frage speziell die Lebensdauer eines Objekts auf eine Weise steuert das überschreitet Bereichsgrenzen.

    #ifdef VISUALIZE_OUTPUT 
        VisualizerObject vis_object; 
    #endif 
    
    #ifdef VISUALIZE_OUTPUT 
        vis_object.initscene(objects_here); 
    #endif 
    

    Der Compiler wird jede Nutzung von vis_object diagnostizieren, die nicht in #ifdef/#endif.

    Die große Kritik ist natürlich, dass die Verwendung des Präprozessors als schlechte Übung in C++ gilt. Der Vorteil besteht darin, dass der Ansatz auch dann verwendet werden kann, wenn es nicht möglich ist, die VisualizerObject-Klasse zu modifizieren (z. B. weil sie in einer Bibliothek eines Drittanbieters ohne bereitgestellten Quellcode ist).

    Dies ist jedoch die einzige Option, die das vom OP angeforderte Feature der Objektlebensdauer über Bereichsgrenzen hinweg anfordert.

    Wenn es möglich ist, die VisualizerObject Klasse zu ändern, macht es zu einem Vorlage mit zwei Spezialisierungen

    template<bool visualise> struct VisualizerObject 
    { 
        // implement all member functions required to do nothing and have no members 
    
        VisualizerObject() {}; 
        void initscene(types_here) {};  
    }; 
    
    template<> struct VisualizerObject<true> // heavyweight implementation with lots of members 
    { 
        VisualizerObject(): heavy1(), heavy2() {}; 
        void initscene(types_here) { expensive_operations_here();}; 
    
        HeavyWeight1 heavy1; 
        HeavyWeight2 heavy2; 
    }; 
    
    int main() 
    { 
         VisualizerObject<VISUALIZE_OUTPUT> vis_object; 
         ... 
         vis_object.initscene(objects_here); 
         ... 
         vis_object.drawScene(objects_here); 
    } 
    

    Die oben wird in allen C++ Versionen arbeiten. Im Wesentlichen funktioniert es entweder durch Instantiierung eines Lightweight-Objekts mit Member-Funktionen, die nichts tun, oder Instanziierung der Schwergewichtsversion.

    Es wäre auch möglich, den obigen Ansatz zu verwenden, um eine VisualizerObject zu wickeln.

    template<bool visualise> VisualizerWrapper 
    { 
         // implement all required member functions to do nothing 
         // don't supply any members either 
    } 
    
    template<> VisualizerWrapper<true> 
    { 
         VisualizerWrapper() : object() {}; 
    
         // implement all member functions as forwarders 
    
        void initscene(types_here) { object.initscene(types_here);}; 
    
         VisualizerObject object; 
    } 
    
    int main() 
    { 
         VisualizerWrapper<VISUALIZE_OUTPUT> vis_object; 
         ... 
         vis_object.initscene(objects_here); 
         ... 
         vis_object.drawScene(objects_here); 
    } 
    

    Der Nachteil von beiden der Vorlage Ansätze ist Wartung - wenn eine Elementfunktion einer Klasse (Template-Spezialisierung) Zugabe ist es notwendig, eine Funktion mit der gleichen Signatur auf den anderen hinzuzufügen. In großen Teameinstellungen ist es wahrscheinlich, dass das Testen/Erstellen meistens mit einer Einstellung von VISUALIZE_OUTPUT oder der anderen erfolgt - so ist es leicht, eine Version aus der Ausrichtung (unterschiedliche Schnittstelle) zu der anderen zu bekommen. Probleme (z. B. ein fehlgeschlagener Build beim Ändern der Einstellung) treten wahrscheinlich zu ungünstigen Zeitpunkten auf - zum Beispiel dann, wenn eine enge Frist für die Lieferung einer anderen Version des Produkts besteht.

    Pedantisch ist der andere Nachteil der Vorlage Optionen, dass sie mit der gewünschten „Art von Muster“, das heißt die if in

    if(VISUALIZE_OUTPUT) 
    { 
        vis_object.initscene(objects_here); 
    } 
    

    und Objektlebensdauern nicht nicht kreuzen Umfang Grenzen erforderlich nicht entsprechen .

    Verwandte Themen