考虑以下两个类:
class LunchBox
{
public:
std::vector<Apple> m_apples;
};
和
class ClassRoom
{
public:
std::vector<Student> m_students;
};
类是相似的,因为它们都包含对象的成员变量向量;然而,它们是不相似的,因为向量的对象是不同的,并且成员变量具有不同的名称。
我想编写一个模板,它将LunchBox
或ClassRoom
作为模板参数(或其他一些参数)和相同类型的现有对象(类似于{{1} })。模板将返回一个对象,该对象添加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);
或LunchBox
类。 是否可以编写此类模板?
答案 0 :(得分: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);
答案 1 :(得分: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
的特化。但是,特征模式是一件好事,因为您现在可以在其他情境中静态反映LunchBox
和ClassRoom
。解耦这些部分可能很有用。operator[]
使用getNthElement
代替Accessor
会更加惯用C ++。然后,您可以直接索引访问者对象。AccessorTraits
对于特质类型来说真的不是一个好名字,但我无法想出更好的东西。这不是访问者的特征,而是其他两个相关类的特征 - 但是这两个类的概念甚至是什么? (也许是SchoolRelatedContainerTraits
?看起来有点罗嗦......)答案 2 :(得分: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;
}
您可以编写适用于LaunchBox
和ClassRoom
的通用代码,而无需编写任何其他专业知识。但是,编写函数重载是一种特殊化的形式。
另一个选项是使用
更新LaunchBox
和ClassRoom
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);
答案 3 :(得分: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;
}
我不确定是否有一个解决方法是从这个函数可以使用的每个不同的类中获得一个不同的变量名;但是,如果有,我还没有想到它。我可以继续研究这个问题,看看能不能改进它;但这是我到目前为止所提出的。