模板的成员变量

模板的成员变量

问题描述:

考虑以下两类:模板的成员变量

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类。 写这样一个模板可能吗?

您可以最小化的具有用于每个类写入的代码量 - 它并没有成为一个模板特殊化,它不必须是整个班级。

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

一个简单的方法做,这是定义具有只是使每种情况下的不同的信息专业化的性状结构。然后,你必须使用此特征类型的模板类:

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

你说:

我想做到这一点,而无需编写模板特为每个类

我不知道为什么这是一个约束。不清楚的是你还有什么是不允许使用的。

如果你可以使用几个函数重载,你可以得到你想要的。

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

我使用一些基本的类做了一个试验案例样本:

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查找)。” –