2017-07-10 12 views
1

Ich möchte eine Karte verwenden, um sich im Wesentlichen um einen Typspezifizierer meinen Code von mehreren Verwendungen vonKarte von int Typspezifizierer

std::unique_ptr<Class>(new class1); 

zu

std::unique_ptr<Class>(new sampleMap[enum1]); 

und definieren Sie dann meine Karte zu verkürzen so dass es jeden enum-Wert (enum1, enum2, ...) auf meine Klassen bezieht (class1, class2, ...).

Aber ich kann meine Karte nicht definieren mit den Werten ein Typnamen wie diese

std::map < int, Class > mapName { 
{0, class1}, 
{0, class1}, 
... 
}; 

ist, da Typname in Karten ist nicht erlaubt.

Der Hauptgrund, warum ich mich für eine Antwort suche dies ist mein Code prägnanter zu machen, indem eine Reihe von „if/else if“ Aussagen oder „switch -Fall“ Aussagen in nur eine Zeile Code zu ersetzen, wo der Ausgang std::unique_ptr<Class>(new class1); wird dynamisch über die von mir definierte Map ermittelt. Also gebe ich einfach die Enumerationsnummer ein und lasse die entsprechende Klasse für mich instanziiert werden. Ansonsten würde ich dies tun:

if (enum1 = 0) 
{ 
    std::unique_ptr<Class>(new class1); 
} 
else if (enum2 = 0) 
{ 
    std::unique_ptr<Class>(new class2); 
} 

(oder eine switch-case) Aber ich will alles oben in einer Zeile wie dies zu tun:

std::unique_ptr<Class>(new sampleMap[enum1]); 

und die Karte Erklärung.

Jeder Hinweis, wie dies getan werden könnte?

+0

Was wollen Sie eigentlich zu tun? Dies scheint wie ein [XY Problem] (https://meta.stackexchange.com/q/66377/218012) – Justin

+0

Es ist möglich, mit Vorlagen, um 'neuen type_map :: Arbeit Baumuster zur, und dann können Sie Vorlage verwenden Rekursion zum Durchlaufen einer Anzahl von Werten. Aber ich stimme Justin zu, sag uns, was du erreichen willst, es könnte einen noch besseren Weg geben. –

+0

Es hört sich so an, als könnte dies ein Kandidat für ein Template-Type-Parameter-Pack sein. –

Antwort

2

Sie können nicht einfach eine std::map implementieren, die Typen als Werte zurückgibt, so wie Sie es versuchen. Sie müssten Ihre eigene Klasse implementieren, die Typen als Werte darstellt. Da Ihr Ziel jedoch scheint, Instanzen von Objekten zu erstellen, bei denen der konkrete Typ von einem Wert abhängt, besteht eine einfache Lösung darin, stattdessen eine Zuordnung von Funktionen zu erstellen. Dies setzt voraus, dass alle Typen, die Sie unterstützen möchten, von einem gemeinsamen Typ abgeleitet sind. Jeder Wert kann eine Funktion enthalten, die das korrekte Objekt konstruiert. Wenn Ihre Typen nicht von einem gemeinsamen Typ abgeleitet sind, müssen Sie einen weiteren Typ löschen (vielleicht mit std::any).

#include <functional> 
#include <iostream> 
#include <map> 
#include <memory> 

// Simple set of classes 
// Class is the base type 
// Class1 and Class2 derive from Class 
struct Class { virtual void func() = 0; }; 
struct Class1 : Class { 
    void func() override { std::cout << "Class1\n"; } 
}; 
struct Class2 : Class { 
    void func() override { std::cout << "Class2\n"; } 
}; 

// A map of factory functions 
const std::map<int, std::function<std::unique_ptr<Class>()>> mapName = { 
    { 1, []() {return std::make_unique<Class1>(); } }, 
    { 2, []() {return std::make_unique<Class2>(); } } 
}; 

int main() 
{ 
    auto foo = mapName.at(2)(); // Make an object of type associated with the value 2 
    foo->func();    // Prints "Class2\n" 
    return 0; 
} 
+0

Meine einzige Absicht ist die Klasseninstanziierung und -rückgabe, die innerhalb der Kartendefinition stattfinden sollte. Also muss ich nur foo zurückgeben und es wird es tun! Danke @Francois –

1

Je nachdem, wo Sie wollen, diesen Code verwenden, könnte man dies mit einer if-else Kette tun wollen. std::function s sind in der Regel sehr schwierig für den Compiler zu optimieren, so dass, wenn Sie diesen Code erwarten oft genug genannt zu werden, ist es wahrscheinlich effizienter es nur aus Code:

(mit @FrançoisAndrieux's example)

#include <iostream> 
#include <memory> 
#include <stdexcept> 

// Simple set of classes 
// Class is the base type 
// Class1 and Class2 derive from Class 
struct Class { 
    virtual void func() = 0; 
}; 
struct Class1 : Class { 
    void func() override { std::cout << "Class1\n"; } 
}; 
struct Class2 : Class { 
    void func() override { std::cout << "Class2\n"; } 
}; 

std::unique_ptr<Class> make_class(int i) 
{ 
    if (i == 0) return std::make_unique<Class1>(); 
    else if (i == 1) return std::make_unique<Class2>(); 

    throw std::out_of_range{ "Asked to construct an unknown type" }; 
} 

int main() 
{ 
    auto foo = make_class(1); // Make an object of type associated with the value 1 
    foo->func(); // Prints "Class2\n" 
    return 0; 
} 

Wenn die Anzahl der Werte groß ist, könnten Sie gewinnen durch eine binäre Suche zu tun (oder nur switch):

// If there are 128 elements, for example 
if (!(0 <= i && i < 128)) throw std::out_of_range{ "..." }; 

if (i < 64) { 
    if (i < 32) { 
     ... 
    } else { 
     ... 
    } 
} else { 
    ... 
} 

es ist chaotisch, aber es ist nur an einem Ort.

+0

Ich möchte verhindern, dass Anweisungen wie ich in der Frage erwähnt. Der Hauptgrund ist die Kürze. Sonst hast du recht. Es würde mit if oder switch-Anweisungen funktionieren. –

+0

@VahidZadeh Wenn Sie die Codegröße beider Ansätze vergleichen, werden Sie feststellen, dass sie im Grunde gleich sind. – Justin

+0

Das stimmt wahrscheinlich. Aber mein Code wird besser lesbar sein, wenn ich nur eine Map statt mehrerer if-Statements definieren muss. Nochmals, das liegt hauptsächlich daran, dass ich nach Kürze und Lesbarkeit suche. Andernfalls kann Ihr Vorschlag auch den Trick tun. –

0

Um eine optimierbar Version zu machen, können Sie einige minimale metaprogramming/Ausdrucksvorlagen tun:

#include <iostream> 
#include <memory> 
#include <stdexcept> 
#include <type_traits> 
#include <utility> 

// Simple set of classes 
// Class is the base type 
// Class1 and Class2 derive from Class 
struct Class { 
    virtual void func() = 0; 
}; 
struct Class1 : Class { 
    void func() override { std::cout << "Class1\n"; } 
}; 
struct Class2 : Class { 
    void func() override { std::cout << "Class2\n"; } 
}; 

template<typename R, typename SwBase, typename T, typename F> 
struct Switch 
{ 
    SwBase base; 
    T value; 
    F fn; 

    constexpr Switch(SwBase base, T value, F fn) 
     : base{ std::move(base) } 
     , value{ std::move(value) } 
     , fn{ std::move(fn) } 
    {} 

    constexpr R operator()(T val) const { 
     if (value == val) return fn(); 
     return base(val); 
    } 
}; 

template<typename R, typename SwBase, typename T, typename F> 
constexpr auto make_switch_impl(SwBase&& swb, T&& t, F&& f) 
{ 
    return Switch<R, std::decay_t<SwBase>, std::decay_t<T>, std::decay_t<F>> { 
     std::forward<SwBase>(swb), 
     std::forward<T>(t), 
     std::forward<F>(f), 
    }; 
} 

template<typename R> 
constexpr auto make_switch(char const* failMsg) 
{ 
    return [=](auto&&) -> R { throw std::out_of_range{ failMsg }; }; 
} 

template<typename R, typename T, typename F, typename... Args> 
constexpr auto make_switch(char const* failMsg, T&& val, F&& fn, Args&&... args) 
{ 
    return make_switch_impl<R>(
     make_switch<R>(failMsg, std::forward<Args>(args)...), 
     std::forward<T>(val), 
     std::forward<F>(fn) 
    ); 
} 

auto make_class(int i) 
{ 
    return make_switch<std::unique_ptr<Class>>(
     "Asked to construct an unknown type", 
     0, [] { return std::make_unique<Class1>(); }, 
     1, [] { return std::make_unique<Class2>(); } 
    )(i); 
} 

int main() 
{ 
    auto foo = make_class(1); // Make an object of type associated with the value 1 
    foo->func(); // Prints "Class2\n" 
    return 0; 
} 

Die switch-Anweisung in das verwandeln würde:

auto make_class(int i) 
{ 
    return make_switch<std::unique_ptr<Class>>(
     "Asked to construct an unknown type", 
     0, [] { return std::make_unique<Class1>(); }, 
     1, [] { return std::make_unique<Class2>(); } 
    )(i); 
} 

Sie auch konnte Speichern Sie den "Schalter" separat, obwohl dies die Option weniger optimierbar macht (bis auf ungefähr das gleiche Niveau wie François Andrieux's solution):

const auto mapName = make_switch<std::unique_ptr<Class>>(
    "Asked to construct an unknown type", 
    0, [] { return std::make_unique<Class1>(); }, 
    1, [] { return std::make_unique<Class2>(); } 
); 

auto make_class(int i) 
{ 
    return mapName(i); 
} 

Diese Version, und auch roh if-else-Ketten, lassen Sie den Compiler die make_class Funktion auf das Äquivalent einer switch Anweisung optimieren. Auch die Hauptfunktion:

int main() 
{ 
    auto foo = make_class(1); // Make an object of type associated with the value 1 
    foo->func(); // Prints "Class2\n" 
    return 0; 
} 

Kann auf das Äquivalent optimiert werden:

int main() 
{ 
    std::cout << "Class2\n"; 
    return 0; 
} 

Die Speicherung der std::function oder die anderen weniger effizient Tricks, die ich erwähnt habe, macht es viel schwieriger für den Compiler um es vollständig zu optimieren (ich habe keinen gefunden, der das tut).

Beachten Sie, dass aus GCC, Clang, Visual C++ und the Intel compiler, nur Clang konnte vollständig die Hauptfunktion mit dieser Switch Struktur optimieren. GCC und Visual C++ konnten es zu einem Anruf Class2func() optimieren. Der Intel-Compiler scheint nicht es überhaupt optimiert haben (aber vielleicht weiß ich nicht die richtigen Flaggen für sie)

Verwandte Themen