2017-05-07 20 views
5

Rozważmy następujące dwie klasy:szablonu zmienną członkiem

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

i

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

Klasy są podobne w tym, że oba zawierają członek zmienny wektor obiektów; jednak są one różne, ponieważ obiekty wektorowe są różne, a zmienne składowe mają różne nazwy.

chciałbym napisać szablon, który zaczyna albo LunchBox lub ClassRoom jako argument szablonu (lub jakiegoś innego parametru) oraz istniejącego obiektu tego samego typu (podobne do std::shared_ptr). Szablon zwróci obiekt dodający funkcję składową getNthElement(int i);, aby poprawić dostęp do metod. Wykorzystanie byłoby jak:

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

Chciałbym zrobić to bez specjalizacje pisanie szablonów dla każdej klasy (które prawdopodobnie będzie wymagać określające zmienną składową do działania na w pewnym sensie). Najlepiej nie chcę modyfikować klas LunchBox lub ClassRoom. Czy można napisać taki szablon?

Odpowiedz

5

Można zminimalizować ilość kodu, który ma być zapisany dla każdej klasy - to nie musi być specjalizacja szablonu i nie muszą być całą klasą.

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); 

Prosta funkcja przeciążenia pomocnik powinien być idealnie w tej samej przestrzeni nazw jako typ, aby uczynić argumentu zależne od odnośników pracy (aka Koenig odnośników).

Możliwe jest również określenie elementu w miejscu budowy, jeśli wolisz, aby to zrobić:

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

Prostym sposobem na zrobienie tego jest zdefiniowanie struktury cechy, która ma specjalizacje z informacjami, które powodują, że każdy przypadek jest inny. Wtedy masz klasę szablonu, który używa tego typu cech:

// 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; 
}; 

Kilka Uwagi:

  • W tym przypadku realizacja będzie technicznie być krótszy bez typu cechami; z tylko specjalnościami Accessor dla każdego typu. Jednak wzór cech jest dobrą rzeczą do nauczenia się, ponieważ teraz masz możliwość statycznego odzwierciedlenia na LunchBox i ClassRoom w innych kontekstach. Rozdzielenie tych elementów może być przydatne.
  • Byłoby bardziej idiomatyczne C++, aby użyć operator[] zamiast getNthElement dla Accessor. Następnie możesz bezpośrednio indeksować obiekty dostępowe.
  • AccessorTraits Naprawdę nie jest to dobre imię dla typu cech, ale mam problem z wymyślaniem czegoś lepszego. Nie jest to cechą osób przystępujących, ale cechami pozostałych dwóch odpowiednich klas - ale jakiej koncepcji dotyczą nawet te dwie klasy? (? Może SchoolRelatedContainerTraits wydaje się nieco rozwlekły ...)
+0

Wolałbym unikać specjalizacji szablonu, jeśli to możliwe. Czy nie ma na to sposobu? Dziękuję za odpowiedź! – chessofnerd

+0

@chessofnerd Nie, chyba że chcesz zastosować metodę dostępową bezpośrednio w menu 'LunchBox' lub' ClassRoom'. Musi być jakiś sposób sprawdzenia akcesora dla danego 'T'. – cdhowie

+0

Tego właśnie się bałem. Miałem nadzieję, że możesz zrobić coś takiego jak "auto accessorizedLunchBox = Accessor (lunchBox)', aby określić zmienną składową do działania. Czy istnieje poparcie dla takiej składni? Byłbym zaskoczony, gdyby tak było. – chessofnerd

2

Mówiłeś:

Chciałbym to zrobić bez pisania specjalizacji szablonu dla każdej klasy

Nie jestem pewien, dlaczego jest to ograniczenie. Nie jest jasne, czego jeszcze nie wolno używać.

Jeśli możesz użyć kilku przeciążeń funkcji, możesz dostać to, co chcesz.

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

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

można napisać kod rodzajowy, który działa zarówno LaunchBox i ClassRoom bez pisania jakichkolwiek innych specjalności. Jednak przeciążanie funkcji pisania jest formą specjalizacji.


Inną opcją będzie zaktualizować LaunchBox i ClassRoom z

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

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

a następnie, skorzystać z faktu, że

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

jest konstrukt prawny. Następnie poniższa klasa będzie działać dobrze.

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_; 
}; 

i można go używać jako:

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

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

robiłam próbkę przypadku test przy użyciu kilku podstawowych klas:

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_; 
}; 

Jednak dla funkcji szablonu, że napisałem zrobiłem jednak należy zmienić nazwę kontenerów w każdej klasie, aby pasowały do ​​tego, aby działało, ponieważ jest to funkcja mojego szablonu:

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

I to moje główne wygląda następująco:

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; 
} 

Nie jestem pewien, czy jest to praca całego mieć inną nazwę zmiennej z każdej innej klasy funkcja ta może wykorzystać; ale jeśli tak, to jeszcze tego nie rozgryzłem. Mogę kontynuować pracę nad tym, aby sprawdzić, czy mogę to poprawić; ale do tego doszedłem do tej pory.

+1

Dzięki! Zobacz odpowiedź @Ben Voight na sprytny sposób robienia tego. – chessofnerd

+1

Ah w porządku; tego właśnie mi brakowało, że Ben Voight wyraźnie stwierdził: "Proste przeciążenie funkcji pomocnika powinno znajdować się w tej samej przestrzeni nazw co typ, aby wykonywać zależne od argumentów operacje wyszukiwania (np. wyszukiwanie Koeniga)." –