我通常面临这样的情况:我引入了一个抽象基类(称为Foo
)来存储容器中不同子类(称为Bar
和Baz
)的实例(例如,std::vector<std::unique_ptr<Foo>>
)。为了便于说明,我将这些示例类放在这里:
class Foo {
public:
virtual int getId() const = 0;
};
class Bar : public Foo {
public:
Bar(int id) : id_(id) {}
int getId() const override { return id_; }
private:
int id_;
};
class Baz : public Foo {
public:
Baz(int id) : id_(id) {}
int getId() const override { return id_; }
private:
int id_;
};
如果我实现一个迭代std::vector<std::unique_ptr<Foo>>
的函数,它看起来像
template<class InputIterator>
void printIds(InputIterator first, InputIterator last) {
for (; first != last; ++first)
std::cout << (*first)->getId() << std::endl;
}
但是,如果我还想允许迭代齐次类型的向量(例如std::vector<Bar>
)而不重写整个函数(或类似类型的其他可能的其他函数),该怎么办?我看到两个明显的可能性:
1)实现功能
template<class Type>
const Type & dereference(const Type &value) {
return value;
}
template<class Type>
const Type & dereference(const std::unique_ptr<Type> &value) {
return *value;
}
并替换
std::cout << (*first)->getId() << std::endl;
通过
std::cout << dereference(*first).getId() << std::endl;
2)实施功能
template<class Type>
int getId(const Type &value) {
return value.getId();
}
template<class Type>
int getId(const std::unique_ptr<Type> &value) {
return value->getId();
}
并替换
std::cout << (*first)->getId() << std::endl;
通过
std::cout << getId(*first) << std::endl;
选项1)似乎是处理Type &
(或const Type &
)和std::unique_ptr<Type>
(或甚至Type *
或const Type *
类型的引用的一般可能性均匀。但是,我还没有看到它在生产代码中被广泛使用。这是避免代码重复的常见模式吗?或者有更好的方法来解决这个问题吗?
答案 0 :(得分:2)
我写get_ptr<T>
。
get_ptr<T*>
如果失败则返回nullptr。
如果传递了对可转换为T*
的内容的引用,则返回它。
如果传递对可转换为T&
的内容的引用,则返回指向它的指针。
否则,如果传递了一个指针,如果指针为空则返回null,如果指针不为空则返回get_ptr<T>(*ptr)
。
否则,如果x.get()
返回指针,则返回get_ptr<T>(x.get())
。
现在printIds
读取:
template<class InputIterator>
void printIds(InputIterator first, InputIterator last) {
for (; first != last; ++first)
std::cout << get_ptr<Foo>(*first)->getId() << std::endl;
}
请注意get_ptr
失败的可能性在这里相当明确。
如果您不想对T
类型进行硬编码,我们可以更进一步。
如果get_obj_ptr
传递指向指针的指针,并且指针不为空,则取消引用指针并递归。如果为null,则返回nullptr强制转换为相同的类型。
如果传递了.get()
返回指针的类,则会在get_obj_ptr(x.get())
上进行递归。
否则,如果get_obj_ptr(x)
传递指针,则返回x
。
否则,如果get_obj_ptr(x)
传递了左值,则返回std::addressof(x)
。
与您的代码不同,它承认失败的可能性。
template<class InputIterator>
void printIds(InputIterator first, InputIterator last) {
for (; first != last; ++first)
auto* ptr = get_obj_ptr(*first);
if (ptr)
std::cout << ptr->getId() << std::endl;
else
std::cout << "null object" << std::endl;
}
作为一般规则,采用智能指针并假设它们永远不为空是一个坏主意。