2017-05-07 9 views
5

Betrachten Sie die folgenden zwei Klassen:Vorlage eine Membervariable

class LunchBox 
{ 
    public: 
    std::vector<Apple> m_apples; 
}; 

und

class ClassRoom 
{ 
    public: 
    std::vector<Student> m_students; 
}; 

Die Klassen gleich sind, dass sie sowohl eine Membervariable Vektor-Objekte enthalten; Sie unterscheiden sich jedoch darin, dass die Objekte des Vektors unterschiedlich sind und die Elementvariablen unterschiedliche Namen haben.

Ich mag eine Vorlage schreiben, die entweder LunchBox oder ClassRoom als Templat Argument (oder einen anderen Parameter) und ein vorhandenes Objekt desselben Typs (ähnlich ein std::shared_ptr) führt. Die Vorlage würde ein Objekt zurückgeben, das eine getNthElement(int i);-Mitgliedsfunktion hinzufügt, um den Zugriff auf die Methoden zu verbessern. Usage wäre wie:

// lunchBox is a previously initialized LunchBox 
// object with apples already pushed into m_apples 
auto lunchBoxWithAccessor = MyTemplate<LunchBox>(lunchBox); 
auto apple3 = lunchBoxWithAccessor.getNthElement(3); 

Ich möchte diese ohne Schreiben Vorlage Spezialisierungen für jede Klasse tun (was wahrscheinlich die Membervariable würde erfordern die Angabe in irgendeiner Weise zu operieren). Vorzugsweise möchte ich die Klassen LunchBox oder ClassRoom nicht ändern. Ist das Schreiben einer solchen Vorlage möglich?

Antwort

5

Sie die Menge an Code minimieren, die für jede Klasse geschrieben werden muss - es ist nicht eine Vorlage Spezialisierung sein muss und es nicht muss eine ganze Klasse sein.

class LunchBox 
{ 
    public: 
    std::vector<Apple> m_apples; 
}; 

class ClassRoom 
{ 
    public: 
    std::vector<Student> m_students; 
}; 

// you need one function per type, to provide the member name 
auto& get_associated_vector(Student& s) { return s.m_apples; } 
auto& get_associated_vector(ClassRoom& r) { return r.m_students; } 

// and then the decorator is generic 
template<typename T> 
class accessor_decorator 
{ 
    T& peer; 
public: 
    auto& getNthElement(int i) { return get_associated_vector(peer).at(i); } 

    auto& takeRandomElement(int i) { ... } 

    // many more ways to manipulate the associated vector 

    auto operator->() { return &peer; } 
}; 

LunchBox lunchBox{}; 
accessor_decorator<LunchBox> lunchBoxWithAccessor{lunchBox}; 
auto apple3 = lunchBoxWithAccessor.getNthElement(3); 

Die einfache Hilfsfunktion Überlastung sollte idealerweise im gleichen Namensraum wie der Typ sein, Argument-abhängige Lookup Arbeit (aka Koenig-Lookup) zu machen.

Es ist auch möglich, das Mitglied am Punkt der Konstruktion zu spezifizieren, wenn Sie es vorziehen, das zu tun:

template<typename T, typename TMemberCollection> 
struct accessor_decorator 
{ 
    // public to make aggregate initialization work 
    // can be private if constructor is written 
    T& peer; 
    TMemberCollection const member; 

public: 
    auto& getNthElement(int i) { return (peer.*member).at(i); } 

    auto& takeRandomElement(int i) { ... } 

    // many more ways to manipulate the associated vector 

    auto operator->() { return &peer; } 
}; 

template<typename T, typename TMemberCollection> 
auto make_accessor_decorator(T& object, TMemberCollection T::*member) 
    -> accessor_decorator<T, decltype(member)> 
{ 
    return { object, member }; 
} 

LunchBox lunchBox{}; 
auto lunchBoxWithAccessor = make_accessor_decorator(lunchBox, &LunchBox::m_apples); 
auto apple3 = lunchBoxWithAccessor.getNthElement(3); 
2

Ein einfacher Weg, dies zu tun, ist eine Merkmalsstruktur zu definieren, die Spezialisierungen nur mit der Information hat, die jeden Fall unterscheidet. Dann haben Sie eine Template-Klasse, die diese Züge Typ verwendet:

// Declare traits type. There is no definition though. Only specializations. 
template <typename> 
struct AccessorTraits; 

// Specialize traits type for LunchBox. 
template <> 
struct AccessorTraits<LunchBox> 
{ 
    typedef Apple &reference_type; 

    static reference_type getNthElement(LunchBox &box, std::size_t i) 
    { 
     return box.m_apples[i]; 
    } 
}; 

// Specialize traits type for ClassRoom. 
template <> 
struct AccessorTraits<ClassRoom> 
{ 
    typedef Student &reference_type; 

    static reference_type getNthElement(ClassRoom &box, std::size_t i) 
    { 
     return box.m_students[i]; 
    } 
}; 

// Template accessor; uses traits for types and implementation. 
template <typename T> 
class Accessor 
{ 
public: 
    Accessor(T &pv) : v(pv) { } 

    typename AccessorTraits<T>::reference_type getNthElement(std::size_t i) const 
    { 
     return AccessorTraits<T>::getNthElement(v, i); 
    } 

    // Consider instead: 
    typename AccessorTraits<T>::reference_type operator[](std::size_t i) const 
    { 
     return AccessorTraits<T>::getNthElement(v, i); 
    } 

private: 
    T &v; 
}; 

Ein paar Anmerkungen:

  • In diesem Fall wird die Umsetzung technisch kürzer wäre ohne Züge Typ; mit nur Spezialisierungen von Accessor für jeden Typ. Allerdings ist das Merkmal Muster eine gute Sache zu lernen, da Sie jetzt eine Möglichkeit haben, in anderen Kontexten auf LunchBox und ClassRoom zu reflektieren. Das Entkoppeln dieser Teile kann nützlich sein.
  • Es wäre mehr idiomatische C++ zu verwenden operator[] anstelle von getNthElement für Accessor. Dann können Sie die Accessor-Objekte direkt indizieren.
  • AccessorTraits ist wirklich kein guter Name für die Merkmale Typ, aber ich habe Probleme, etwas besseres zu finden. Es sind nicht die Eigenschaften der Accessoren, sondern die Eigenschaften der beiden anderen relevanten Klassen - aber welches Konzept bezieht sich auf diese beiden Klassen? (? Vielleicht SchoolRelatedContainerTraits scheint ein wenig wortreich ...)
+0

Ich würde es vorziehen, die Template-Spezialisierung möglichst zu vermeiden. Gibt es keinen Weg um es herum? Danke für die Antwort! – chessofnerd

+0

@chessofnerd Nicht, wenn Sie die Accessor-Methode direkt auf 'LunchBox' oder' ClassRoom' implementieren möchten. Es muss einen Weg geben, den Accessor für ein gegebenes "T" nachzuschlagen. – cdhowie

+0

Das war, wovor ich Angst hatte. Ich hatte gehofft, du könntest etwas wie 'auto accessorizedLunchBox = Accessor (lunchBox) 'machen, um die Membervariable anzugeben, die bearbeitet werden soll. Gibt es irgendeine Unterstützung für eine solche Syntax? Ich wäre überrascht, wenn es da wäre. – chessofnerd

2

Sie sagte:

Ich möchte, dies zu tun, ohne Vorlage Spezialisierungen schreiben für jede Klasse

Ich bin mir nicht sicher, warum das eine Einschränkung ist. Was nicht klar ist, was darf man sonst nicht benutzen?

Wenn Sie einige Funktionsüberlastungen verwenden dürfen, können Sie bekommen, was Sie wollen.

std::vector<Apple> const& getObjects(LunchBox const& l) 
{ 
    return l.m_apples; 
} 

std::vector<Student> const& getObjects(ClassRoom const& c) 
{ 
    return c.m_students; 
} 

Sie generischen Code schreiben können, ohne das Schreiben weitere Spezialisierungen mit beiden LaunchBox und ClassRoom arbeitet. Das Schreiben von Funktionsüberladungen ist jedoch eine Form der Spezialisierung.


wird eine weitere Option LaunchBox und ClassRoom mit

class LunchBox 
{ 
    public: 
    std::vector<Apple> m_apples; 
    using ContainedType = Apple; 
}; 

class ClassRoom 
{ 
    public: 
    std::vector<Student> m_students; 
    using ContainedType = Apple; 
}; 

und dann nehmen Sie sich die Tatsache zunutze, dass

LaunchBox b; 
std::vector<Apple>* ptr = reinterpret_cast<std::vector<Apple>*>(&b); 

juristisches Konstrukt zu aktualisieren sein soll. Dann wird die folgende Klasse funktionieren.

template <typename Container> 
struct GetElementFunctor 
{ 
    using ContainedType = typename Container::ContainedType; 

    GetElementFunctor(Container const& c) : c_(c) {} 

    ContainedType const& getNthElement(std::size_t n) const 
    { 
     return reinterpret_cast<std::vector<ContainedType> const*>(&c_)->operator[](n); 
    } 

    Container const& c_; 
}; 

und Sie können es als verwenden:

LunchBox b; 
b.m_apples.push_back({}); 

auto f = GetElementFunctor<LunchBox>(b); 
auto item = f.getNthElement(0); 
1

ich einen Testfall Probe hatte ein paar grundlegende Klassen:

class Apple { 
public: 
    std::string color_; 
}; 

class Student { 
public: 
    std::string name_; 
}; 

class LunchBox { 
public: 
    std::vector<Apple> container_; 
}; 

class ClassRoom { 
public: 
    std::vector<Student> container_; 
}; 

jedoch für die Template-Funktion, die ich geschrieben habe ich Sie müssen jedoch den Namen der Container in jeder Klasse ändern, damit sie funktioniert, da dies meine Vorlagenfunktion ist:

template<class T> 
auto accessor(T obj, unsigned idx) { 
    return obj.container_[idx]; 
} 

Und das ist, was mein Haupt aussieht:

int main() { 

    LunchBox lunchBox; 
    Apple green, red, yellow; 
    green.color_ = std::string("Green"); 
    red.color_ = std::string("Red"); 
    yellow.color_ = std::string("Yellow"); 

    lunchBox.container_.push_back(green); 
    lunchBox.container_.push_back(red); 
    lunchBox.container_.push_back(yellow); 


    ClassRoom classRoom; 
    Student s1, s2, s3; 
    s1.name_ = std::string("John"); 
    s2.name_ = std::string("Sara"); 
    s3.name_ = std::string("Mike"); 

    classRoom.container_.push_back(s1); 
    classRoom.container_.push_back(s2); 
    classRoom.container_.push_back(s3); 

    for (unsigned u = 0; u < 3; u++) { 

     auto somethingUsefull = accessor(lunchBox, u); 
     std::cout << somethingUsefull.color_ << std::endl; 

     auto somethingElseUsefull = accessor(classRoom, u); 
     std::cout << somethingElseUsefull.name_ << std::endl; 
    } 

    return 0; 
} 

Ich bin nicht sicher, ob es eine Arbeit um ist diese Funktion verwenden kann, einen anderen Variablennamen von jeder anderen Klasse zu haben; Aber wenn es da ist, habe ich es noch nicht herausgefunden. Ich kann weiter daran arbeiten, um zu sehen, ob ich es verbessern kann. Aber das ist es, was ich bisher herausgefunden habe.

+1

Danke! Siehe @Ben Voight's Antwort für eine clevere Art und Weise dies zu tun. – chessofnerd

+1

Ah okay; Das ist, was mir gefehlt hat, dass Ben Voight klar gesagt hat: 'Die einfache Hilfsfunktionsüberladung sollte idealerweise im selben Namensraum wie der Typ sein, um argumentabhängige Nachschlagearbeiten zu ermöglichen (aka Koenig lookup) –

Verwandte Themen