2017-05-07 150 views
5

考慮以下兩類:模板的成員變量

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

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

的類是一樣的,因爲它們都包含對象的成員變量向量;然而,它們並不相同,因爲矢量的對象是不同的,並且成員變量有不同的名稱。

我想寫一個模板,採用任一LunchBoxClassRoom用作模板參數(或一些其他參數)和相同類型的現有對象(類似於std::shared_ptr)。該模板將返回一個對象,該對象添加一個getNthElement(int i);成員函數以改進訪問方法。用法是這樣的:

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

我想做到這一點沒有爲每個類寫作模板特(這可能需要指定成員變量以某種方式進行操作)。我最好不要修改LunchBoxClassRoom類。 寫這樣一個模板可能嗎?

回答

5

您可以最小化的具有用於每個類寫入的代碼量 - 它並沒有成爲一個模板特殊化,它不必須是整個班級。

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

的簡單的輔助函數重載理論上應在相同的命名空間類型,使參數相關的查找工作(又稱Koenig查找)。

也有可能在施工點指定成員,如果你喜歡這樣做:

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

一個簡單的方法做,這是定義具有隻是使每種情況下的不同的信息專業化的性狀結構。然後,你必須使用此特徵類型的模板類:

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

的幾個注意事項:

  • 在這種情況下,實施在技術上是沒有一個特質型短;每種類型只有Accessor專業化。然而,特徵模式是一件好事,因爲您現在可以在其他上下文中靜態反映LunchBoxClassRoom。解耦這些碎片可能是有用的。
  • 這將是更慣用C++使用operator[]代替getNthElementAccessor。然後你可以直接索引訪問器對象。
  • AccessorTraits真的不是爲特徵類型一個好聽的名字,但我有想出什麼更好的麻煩。這不是訪問者的特徵,而是其他兩個相關類的特徵 - 但是什麼概念甚至與這兩個類有關? (?也許SchoolRelatedContainerTraits似乎有點羅嗦)
+0

如果可能,我寧願避免模板專門化。有沒有辦法解決它?感謝你的回答! – chessofnerd

+0

@chessofnerd除非您想直接在'LunchBox'或'ClassRoom'上實現訪問器方法,否則不行。對於給定的「T」,必須有一些方法來查看訪問器。 – cdhowie

+0

這就是我所害怕的。我希望你可以做一些像'auto accessorizedLunchBox = Accessor (lunchBox)'來指定要操作的成員變量。有沒有對這種語法的支持?如果有的話,我會很驚訝。 – chessofnerd

2

你說:

我想做到這一點,而無需編寫模板特爲每個類

我不知道爲什麼這是一個約束。不清楚的是你還有什麼是不允許使用的。

如果你可以使用幾個函數重載,你可以得到你想要的。

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

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

您可以編寫既LaunchBoxClassRoom工作,而無需編寫任何其他專長的通用代碼。但是,編寫函數重載是專業化的一種形式。


另一種選擇將是更新LaunchBoxClassRoom

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

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

,然後,採取的事實

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

是一個法律結構的優勢。然後,下面的課程將正常工作。

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

,你可以使用它作爲:

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

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

我使用一些基本的類做了一個試驗案例樣本:

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

但是對於我寫了我做的模板函數但是必須更改每個類中容器的名稱以匹配此工作,因爲這是我的模板函數:

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

這是我的主要看上去像什麼:

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

我不知道是否有一個變通有來自各個不同的階級這個功能可以使用不同的變量名;但如果有的話,我還沒有弄明白。我可以繼續努力,看看我能否改進它;但這是我迄今爲止提出的。

+1

謝謝!看到@Ben Voight的回答是一個聰明的做法。 – chessofnerd

+1

啊好的;我錯過了Ben Voight的清楚說明:「簡單的輔助函數重載應該與類型位於同一個命名空間中,以便進行依賴於參數的查找工作(也稱爲Koenig查找)。」 –