是否可以测试两个迭代器是否指向同一个对象?

时间:2012-07-26 01:53:59

标签: c++ stl iterator

假设我正在创建一个复制值的函数:

template<class ItInput, class ItOutput>
void copy(ItInput i, ItOutput o) { *o = *i; }

如果io指向同一个对象,我想避免分配,因为那时分配毫无意义。

显然,我不能说if (i != o) { ... },因为io可能属于不同类型,因为它们可能指向不同的容器(因此无法比拟)。不太明显,我也不能使用重载的函数模板,因为迭代器可能属于不同的容器,即使它们具有相同的类型。

我对此的初步解决方案是:

template<class ItInput, class ItOutput>
void copy(ItInput i, ItOutput o)
{
    if (&*o != static_cast<void const *>(&*i))
        *o = *i;
}

但我不确定这是否有效。如果*o*i实际返回对象而不是引用,该怎么办?

有没有办法做到这一点?

4 个答案:

答案 0 :(得分:4)

我认为这不是必要的:如果分配很昂贵,那么类型应该定义一个赋值运算符,它执行(相对便宜的)自我分配检查以防止做不必要的工作。但是,这是一个有趣的问题,有许多陷阱,所以我会采取措施来回答它。

如果我们要组装一个适用于输入和输出迭代器的通用解决方案,我们必须注意几个缺陷:

  • 输入迭代器是单通道迭代器:你只能通过迭代器对每个元素执行一次间接,因此,我们不能通过迭代器执行一次间接获取指向值的地址第二次执行复制。

  • 输入迭代器可以是代理迭代器。代理迭代器是一个迭代器,其operator*返回一个对象,而不是一个引用。使用代理迭代器,表达式&*it格式不正确,因为*it是一个右值(它可能会使一元 - &重载,但这样做通常被认为是邪恶和可怕的,并且大多数类型都不这样做。)

  • 输出迭代器只能用于输出;你不能通过它执行间接,并将结果用作右值。您可以写入“指向元素”,但不能从中读取。

所以,如果我们要进行“优化”,我们只需要在两个迭代器都是前向迭代器的情况下(这包括双向迭代器和随机访问迭代器:它们也是前向迭代器) )。

因为我们很好,所以我们还需要注意这样一个事实:尽管它违反了概念要求,但许多代理迭代器都错误地表示了它们的类别,因为拥有支持随机访问的代理迭代器非常有用。在一系列代理对象上。 (我甚至不确定如何在不执行此操作的情况下为std::vector<bool>实现有效的迭代器。)

我们将使用以下标准库标题:

#include <iterator>
#include <type_traits>
#include <utility>

我们定义一个元函数is_forward_iterator,它测试一个类型是否是一个“真正的”前向迭代器(即,不是代理迭代器):

template <typename T>
struct is_forward_iterator :
    std::integral_constant<
        bool,
        std::is_base_of<
            std::forward_iterator_tag,
            typename std::iterator_traits<T>::iterator_category
        >::value &&
        std::is_lvalue_reference<
            decltype(*std::declval<T>())
        >::value>
{ };

为简洁起见,我们还定义了一个元函数can_compare,它测试两个类型是否都是前向迭代器:

template <typename T, typename U>
struct can_compare :
    std::integral_constant<
        bool,
        is_forward_iterator<T>::value &&
        is_forward_iterator<U>::value
    >
{ };

然后,我们将编写copy函数的两个重载,并使用SFINAE根据迭代器类型选择正确的重载:如果两个迭代器都是前向迭代器,我们将包括检查,否则我们' ll排除检查并始终执行分配:

template <typename InputIt, typename OutputIt>
auto copy(InputIt const in, OutputIt const out)
    -> typename std::enable_if<can_compare<InputIt, OutputIt>::value>::type
{
    if (static_cast<void const volatile*>(std::addressof(*in)) != 
        static_cast<void const volatile*>(std::addressof(*out)))
        *out = *in;
}

template <typename InputIt, typename OutputIt>
auto copy(InputIt const in, OutputIt const out)
    -> typename std::enable_if<!can_compare<InputIt, OutputIt>::value>::type
{
    *out = *in;
}

就像馅饼一样简单!

答案 1 :(得分:3)

我认为这可能是一种情况,您可能需要记录一些关于您在函数中期望的类型的假设,并且满足于不完全通用。

operator*一样,operator&可能会重载以执行各种操作。如果你正在防范operator*,那么你应该考虑operator&operator!=等。

我想说强制执行的一个很好的先决条件(通过代码中的注释或一个概念/ static_assert)是operator*返回对迭代器指向的对象的引用而它不是(或不应该)执行副本。在这种情况下,你的代码看起来很好。

答案 2 :(得分:1)

您的代码确实没问题,或者至少对所有迭代器类别都不合适。

输入迭代器和输出迭代器不需要在第一次之后可以解除引用(它们预期是单遍)并且允许输入迭代器取消引用任何“可转换为T”(§24.2) 0.3 / 2)。

因此,如果你想处理所有类型的迭代器,我认为你不能强制执行这种“优化”,即你不能一般性地检查两个迭代器是否指向同一个对象。如果你愿意放弃输入和输出迭代器,那么你应该没问题。否则,在任何情况下我都会坚持做副本(我真的认为你没有其他选择)。

答案 3 :(得分:0)

编写一个帮助器模板函数equals,如果迭代器是不同的类型,它将自动返回false。要么是copy函数本身的专业化或重载,要么。

如果它们属于同一类型,那么您可以使用比较它们所解析的对象指针的技巧,不需要强制转换:

if (&*i != &*o)
    *o = *i;

如果*i*o没有返回引用,则没有问题 - 即使没有,也会发生副本,但不会造成任何损害。