5

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

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

我想避免分配ifio指向同一个对象,因为那样分配是没有意义的。

显然,我不能说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

4 回答 4

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 根据迭代器类型选择正确的重载:如果两个迭代器都是前向迭代器,我们将包含检查,否则我们将排除检查并始终执行任务:

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

像馅饼一样容易!

于 2012-07-26T05:26:44.213 回答
3

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

operator*,operator&可以被重载来做各种各样的事情。如果您要防范operator*,那么您应该考虑operator&andoperator!=等。

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

于 2012-07-26T02:13:40.377 回答
1

按原样,您的代码肯定不行,或者至少对于所有迭代器类别都不好。

输入迭代器和输出迭代器在第一次之后不需要是可取消引用的(它们应该是单次传递的),并且输入迭代器可以取消引用任何“可转换为T”的东西(第 24.2.3/2 节)。

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

于 2012-07-26T02:35:52.853 回答
0

编写一个帮助模板函数,如果迭代器是不同的类型equals,它会自动返回。false要么这样做,要么对你的copy函数本身进行专门化或重载。

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

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

如果*i*o不返回引用,没问题 - 即使不需要复制也会发生,但不会造成任何伤害。

于 2012-07-26T02:42:22.467 回答