【问题标题】:Templated Function that works for iterators over raw pointers as well as iterators over unique_ptrs适用于原始指针上的迭代器以及 unique_ptrs 上的迭代器的模板化函数
【发布时间】:2014-09-13 15:43:17
【问题描述】:

假设我有一个模板函数,它接受某种指针集合的 const 范围(或者更好的开始和结束迭代器)。 这个函数在内部构造了一个带有指针的 STL 容器来重组元素。

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

#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)在将它们都转换为原始指针时的不统一行为。 这可以通过取消引用循环 à la std::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 的迭代器中使用,或者在 boost::transform_iterator 中使用,就像在@Praetorian 的回答中一样。但是,在智能指针实现的特定 get() 成员而不是使指针成为指针的 operator*-interface 上构建仍然感觉很奇怪。

【问题讨论】:

  • 您不想打印e 而不是&amp;e吗?
  • 我想使用自定义对象,但不想实现 operator
  • 啊,你说得对……在基于范围的范围内,因为我已经有了指针……我会编辑。谢谢!
  • 您也可以改用vector&lt;FooIterator&gt;,然后注意排序是根据包含的值而不是FooIterator本身进行的。
  • @pmr。应该可以工作,但这意味着修改函数的内部逻辑。 (并且会为每个新的迭代器类型触发内部使用的容器的额外模板实例化......)

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


【解决方案1】:

这是一种包装指针迭代器的通用方法。在取消引用时,它取消引用存储的迭代器(产生(智能)指针),并再次取消引用(产生对 pointee 的引用),然后返回指针的地址(通过std::addressof())。其余的实现只是迭代器样板。

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

Coliru 上的实时示例(带有std::random_shuffle() 的乐趣)。 :)

【讨论】:

  • auto&amp;&amp; old = *this; 不会复制。你想要在operator++(int) 中的auto old = *this;。此外,operator-&gt; 在技术上应该是 const
  • addressof(*p) 不是 p==nullptr 的未定义行为吗?
  • @ThomasB。大概。为了安全起见,我添加了一个快速的小检查。
【解决方案2】:

您的代码在处理unique_ptr 时的问题是这一行:

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

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

使用boost::indirect_iterator 将产生Foo const&amp;,然后可以将其转换为Foo const *,方法是将其包装在Boost.TransformIterator 中,并将std::addressof 作为一元谓词传递给boost::transform_iterator

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

Live demo

【讨论】:

  • 很好地使用了 boost。但是indirect_iterator 不会为nullptrs 产生未定义的行为吗?
  • @ThomasB。是的,它会
【解决方案3】:

我的 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';
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-22
    • 2015-12-09
    • 1970-01-01
    相关资源
    最近更新 更多