模板化函数,适用于原始指针上的迭代器以及unique_ptrs上的迭代器

时间:2014-07-22 17:22:49

标签: c++ templates c++11 iterator unique-ptr

假设我有一个模板函数,它接受某种指针集合的const范围(或更好的开始和结束迭代器)。 此函数在内部构造一个STL容器,其中包含指针以重新组织元素。

现在我想将此函数重用于unique_ptr-collections。我不知何故需要修改模板参数或引入新的包装器或重载......但是如何? 有没有C ++ 11模板魔术,STL助手或助推助手? 下面是一个示例代码:

#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>

// Element Class
class Foo { };

// Take a range of elements, sort them internally by their addresses and print them in order    
template <typename FooIterator>
void print_sorted_addresses(FooIterator beginFoos, FooIterator endFoos)
{
    // Sort them
    std::vector<const Foo*> elements(beginFoos, endFoos);
    std::sort(elements.begin(), elements.end());
    // Print them
    for(const auto& e : elements)
        std::cout << e << std::endl;
}

int main() {
    std::vector<Foo*> raw_foos;
    std::vector<std::unique_ptr<Foo>> unique_foos;

    // Fill them
    for(int i=0; i<10; i++) {
        std::unique_ptr<Foo> foo(new Foo());
        raw_foos.push_back(foo.get());
        unique_foos.push_back(std::move(foo));
    }

    print_sorted_addresses(raw_foos.cbegin(), raw_foos.cend());
    //print_sorted_Foos(unique_foos.cbegin(), unique_foos.cend()); // ERROR

    return 0;
}

罪魁祸首似乎是原始指针和智能指针(特别是unique_ptr)的非均匀行为,用于将它们转换为原始指针。 这可以通过解引用循环àlastd::addressof(*p)来规避,但如果p不是nullptr,则只有明确定义的行为。 为了减少我使用条件模板进行的任何运行时检查,并提出以下内容:

template<typename Ptr> using RawPtr = typename std::pointer_traits<Ptr>::element_type*; 

// raw pointers like int**, const char*, ...
template<typename Ptr>
typename std::enable_if<std::is_pointer<Ptr>::value, RawPtr<Ptr>>::type make_raw(Ptr ptr) { return ptr; }

// smart pointers like unique_ptr, shared_ptr, ... 
template<typename Ptr>
typename std::enable_if<!std::is_pointer<Ptr>::value, RawPtr<Ptr>>::type make_raw(Ptr& ptr) { return ptr.get(); }

这可以在@tclamb的迭代器中使用,或者在@ Praetorian的答案中用于boost :: transform_iterator。但是构建特定的get() - 智能指针实现的成员而不是操作符* -interface使得指针成为指针仍然感觉很奇怪。

3 个答案:

答案 0 :(得分:3)

这是一个包装指针迭代器的通用方法。在取消引用时,它取消引用存储的迭代器(产生(smart-)指针),并再次取消引用(产生对指针对象的引用),然后返回指针对象的地址(通过{ {1}})。其余的实现只是迭代器样板。

std::addressof()

Coliru上的实例(投放了template<typename Iterator, typename Address = decltype(std::addressof(**std::declval<Iterator>())) > class address_iterator : public std::iterator<std::input_iterator_tag, Address> { public: address_iterator(Iterator i) : i_{std::move(i)} {}; Address operator*() const { auto&& ptr = *i_; return i_ == nullptr ? nullptr : std::addressof(*ptr); }; Address operator->() const { return operator*(); } address_iterator& operator++() { ++i_; return *this; }; address_iterator operator++(int) { auto old = *this; operator++(); return old; } bool operator==(address_iterator const& other) const { return i_ == other.i_; } private: Iterator i_; }; template<typename I, typename A> bool operator!=(address_iterator<I, A> const& lhs, address_iterator<I, A> const& rhs) { return !(lhs == rhs); } template<typename Iterator> address_iterator<Iterator> make_address_iterator(Iterator i) { return i; } 以获得乐趣)。 :)

答案 1 :(得分:1)

处理unique_ptr时代码的问题在于以下几行:

std::vector<const Foo*> elements(beginFoos, endFoos);

vector构造函数将尝试复制unique_ptr s,这是不允许的;并且您对unique_ptr指向的内容感兴趣。因此,您需要额外级别的解除引用才能生成对托管对象的引用。这可以使用Boost.IndirectIterator来实现。

使用boost::indirect_iterator会产生Foo const&,然后可以将其Foo const *转换为boost::transform_iterator,并将Boost.TransformIterator作为一元谓词传递给template <typename FooIterator> void print_sorted_addresses(FooIterator beginFoos, FooIterator endFoos) { std::vector<Foo const *> elements( boost::make_transform_iterator(boost::make_indirect_iterator(beginFoos), std::addressof<Foo>), boost::make_transform_iterator(boost::make_indirect_iterator(endFoos), std::addressof<Foo>)); std::sort(elements.begin(), elements.end()); for(const auto& e : elements) std::cout << e << std::endl; } {{1}}。

{{1}}

std::addressof

答案 2 :(得分:0)

我的2枚硬币

Foo* get(Foo* const& p) {
    return p;
}

Foo* get(std::unique_ptr<Foo> const& up) {
    return up.get();
}


// Take a range of elements, sort them internally by their addresses and print
// them in order    
template <typename ConstIt>
void print_sorted_addresses(const ConstIt& cbegin, const ConstIt& cend) {

    using deref_type = decltype(*cbegin);
    using raw_ptr_type = decltype(get(*cbegin));

    std::vector<raw_ptr_type> v;
    v.reserve(cend - cbegin);

    std::transform(cbegin, cend, 
                   std::back_inserter(v),
                   [] (const deref_type& p) {
                        return get(p);
                   });


    std::sort(v.begin(), v.end());

    for(const auto& p : v)
        std::cout << p << '\n';
}