【问题标题】:Generic for loop for elementary and complex type基本类型和复杂类型的通用 for 循环
【发布时间】:2016-05-17 09:22:38
【问题描述】:

假设我有这两个std::vector

std::vector<int> v_int(1000);
std::vector<T> v_T(1000); // Where T is copy-costy type

如果我需要(单独)循环浏览它们而不需要编辑我可能使用的项目:

for(const auto item:v_int){
    //...
}

for(const auto& item:v_T){ //Note &
    //...
}

使用const auto item:v_T 进行迭代太糟糕了,因为每次迭代都会执行一个副本。然而,使用const auto&amp; item:v_int 不是最佳的,但也不是那么糟糕。因此,如果我需要一个同时处理它们的代码,我曾经使用const auto&amp; item:v

问题:是否有一种通用的方式来编写 for 循环,它会为它们两者使用最好的声明?比如:

template <typename T>
void iterate(const std::vector<T> v){
    for(const auto/*&*/ item:v){ // activate & if T is not elementary type
         //...
    }
}

【问题讨论】:

  • 在 T 是原始类型的情况下,您希望避免引用的任何具体原因?
  • 这种增加的复杂性真的值得为内置类型使用值而不是引用语义而付出努力吗?
  • 为什么不使用普通的迭代器循环,问题就消失了。
  • 你有没有测试过const int&amp;实际上比int慢?我猜智能编译器会为两者生成相同的代码。
  • @HumamHelfawi 我认为最好的做法是编写清晰的代码,并且在发现真正的性能问题之前不要进行微优化。

标签: c++ templates c++11


【解决方案1】:

您可以使用标准类型特征来做到这一点:

template <typename T>
using select_type = std::conditional_t<std::is_fundamental<T>::value,
                                      const T, const T&>;

template <typename T>
void iterate(const std::vector<T> v){
    for(select_type<T> item:v){ // activate & if T is not elementary type
         //...
    }
}

但是,我质疑这样做是否明智。它只是把代码弄乱了一些不太可能产生任何影响的东西。

【讨论】:

【解决方案2】:

当您不在乎时使用auto&amp;&amp;。当你必须保证它没有被编辑时,使用auto const&amp;

引用是别名,在像循环这样的直接上下文中,优化不存在是微不足道的,尤其是在从未修改过值的情况下。

将引用通过编译单元边界传递给函数时会遇到一些困难,这就是为什么有时建议按值传递 int 而不是 const&amp; 的原因,但这并不适用于此。

【讨论】:

    【解决方案3】:

    我不是template 领域的专家,但我认为困扰您的是char 的副本将只复制一个字节,而使用&amp; 将复制sizeof(char*) 字节。

    我想到的是使用sizeof(T)

    在这种情况下,您可以使用

    if (sizeof(T) >= sizeof(void*))
         // use '&'
    else
         // don't use '&'
    

    由于sizeof 在编译时被评估,这个if 分支将被编译器优化出来。

    【讨论】:

    • 判断是否需要&amp; 的有趣方法。通过结合 TartanLlama 答案,我们可以使用:template &lt;typename T&gt; using select_type = std::conditional_t&lt;sizeof(T) &gt;= sizeof(void*), const T, const T&amp;&gt;;
    • 如果你走这条路,你可能想检查类型是否可以简单地复制。
    • @TartanLlama 如果类型的大小小于指针大小,那么它必须是“可简单复制的”,我在这里遗漏了什么吗?...
    • @AlexLop。不必要。例如,该类型可能有一个 char 成员,它表示某个资源的句柄。尺寸会非常小,但如果必须对手柄进行一些处理,复制可能会非常昂贵。 (有点晦涩,我知道,但如果我们是通用的,我们不妨完全通用)
    • @TartanLlama 绝对是晦涩的案例,但我同意,您的解决方案更通用。 C++11 引入了一系列新的可用函数来改进模板开发。由于我很少处理模板,您对std::is_fundamenta&lt;&gt; 的介绍对我来说是一个“发现”:)
    【解决方案4】:

    我个人只会对每种情况使用 const auto& 因为开销可以忽略不计,除非您需要在紧密循环中处理一些非常大的数据集。

    然而,获得你想要的东西的一种方法可能是为你的迭代方法实现模板特化:

    #include <iostream>
    #include <string>
    #include <vector>
    
    template <typename T>
    void iterate(const std::vector<T>& v){
        for(const auto& item:v){
            std::cout << item << std::endl;
        }
    }
    
    template <>
    void iterate(const std::vector<int>& v){
        for(const auto item:v){
             std::cout << "[" << item << "]" << std::endl;
        }
    }
    
    int main()
    {
        std::vector<std::string> strings = { "foo", "bar" };
        std::vector<int> numbers = { 1, 2 };
    
        iterate(strings);
        iterate(numbers);
    }
    

    注意:您更有可能看到将向量复制到迭代函数中会降低性能:)

    【讨论】:

    • 如果有任何开销的话。
    • 但是你必须为每个基本类型实现特化。
    • 你的笔记是什么意思?这些向量是通过引用获取的;这里没有副本。
    • 问题的迭代方法没有。我更新了我的示例代码以使用引用。
    • @Garf365:您可以根据is_fundamental&lt;T&gt; 是否使用标签调度并因此而专门化。
    猜你喜欢
    • 2012-06-30
    • 2015-03-17
    • 1970-01-01
    • 2015-01-25
    • 1970-01-01
    • 2012-11-25
    • 1970-01-01
    • 2022-07-30
    • 1970-01-01
    相关资源
    最近更新 更多