为 std::vector 键入安全索引值

Posted

技术标签:

【中文标题】为 std::vector 键入安全索引值【英文标题】:Type safe index values for std::vector 【发布时间】:2018-03-20 14:51:10 【问题描述】:

我的类从不同的常量 STL 向量中收集索引值。问题是,即使这些向量的内容不同且用途不同,它们的索引也是std::size_t 类型,因此人们可能会错误地使用为一个向量存储的索引来访问另一个向量的元素。当索引未与正确的向量一起使用时,是否可以更改代码以产生编译时错误?

代码示例:

#include <iostream>
#include <string>
#include <vector>

struct Named

    std::string name;
;

struct Cat : Named  ;
struct Dog : Named  ;

struct Range

    std::size_t start;
    std::size_t end;
;

struct AnimalHouse

    std::vector< Cat > cats;
    std::vector< Dog > dogs;
;

int main( )

    AnimalHouse house;
    Range cat_with_name_starting_with_a;
    Range dogs_with_name_starting_with_b;

    // ...some initialization code here...

    for( auto i = cat_with_name_starting_with_a.start;
         i < cat_with_name_starting_with_a.end;
         ++i )
    
        std::cout << house.cats[ i ].name << std::endl;
    

    for( auto i = dogs_with_name_starting_with_b.start;
         i < dogs_with_name_starting_with_b.end;
         ++i )
    
        // bad copy paste but no compilation error
        std::cout << house.cats[ i ].name << std::endl; 
    

    return 0;

免责声明:请不要过分关注示例本身,我知道这很愚蠢,只是为了理解。

【问题讨论】:

只使用迭代器而不是索引 @Slava:这并不能保证解决问题。混合迭代器不会需要导致编译失败。但是,它比数字索引更好。最终,总会有办法解决这个问题。正确的解决方案是(a)不要弄错,(b)测试来检测你什么时候出错。 @Slava 如果向量在内存中被移动或复制,即使它们没有改变元素数量或元素顺序,迭代器也不再正确 没错,那么您可以在std::vector 上编写一个瘦包装器,它接受自定义索引(这是size_t 上的瘦包装器) 您也可以在Range 中存储对容器的引用并通过它访问容器。 【参考方案1】:

这是对我的评论进行跟进的尝试。 当然,有很大的空间可以根据用例更改其工作方式的细节,这种方式对我来说似乎是合理的。

#include <iostream>
#include <vector>

template <typename T>
struct Range 
    Range(T& vec, std::size_t start, std::size_t end) :
        m_vector(vec),
        m_start(start),
        m_end(end),
        m_size(end-start+1) 

    auto begin() 
        auto it = m_vector.begin();
        std::advance(it, m_start);
        return it;
    
    auto end() 
        auto it = m_vector.begin();
        std::advance(it, m_end + 1);
        return it;
    

    std::size_t size() 
        return m_size;
    

    void update(std::size_t start, std::size_t end) 
        m_start = start;
        m_end = end;
        m_size = end - start + 1;
    

    Range copy(T& other_vec) 
        return Range(other_vec, m_start, m_end);
    

    typename T::reference operator[](std::size_t index) 
        return m_vector[m_start + index];
    

    private:
    T& m_vector;
    std::size_t m_start, m_end, m_size;
;

// This can be used if c++17 is not supported, to avoid
// having to specify template parameters
template <typename T>
Range<T> make_range(T& t, std::size_t start, std::size_t end) 
    return Range<T>(t, start, end);


int main() 
    std::vector<int> v1 1, 2, 3, 4, 5;
    std::vector<double> v2 0.5, 1., 1.5, 2., 2.5;

    Range more_then_2(v1, 1, 4); // Only works in c++17 or later
    auto more_then_1 = make_range(v2, 2, 4);

    for (auto v : more_then_2)
        std::cout << v << ' ';

    std::cout << std::endl;

    for (auto v : more_then_1)
        std::cout << v << ' ';

    std::cout << std::endl;

    more_then_2.update(2,4);

    for (auto v : more_then_2)
        std::cout << v << ' ';

    std::cout << std::endl;

    auto v3 = v1;
    auto more_then_2_copy = more_then_2.copy(v3);

    for (unsigned i=0; i < more_then_2_copy.size(); ++i)
        std::cout << more_then_2_copy[i] << ' ';

    return 0;

【讨论】:

很好地使用 std::advance。它让我考虑使用 std::begin 和 std::end,所以这可能更通用。 @nyarlathotep108 好主意。然后,除了operator[],它适用于所有 STL 容器(几乎)和 c 样式数组。操作员可能会被 SFINA 淘汰,或者您可以为没有随机访问的容器制作一个“假”的。

以上是关于为 std::vector 键入安全索引值的主要内容,如果未能解决你的问题,请参考以下文章

如何安全地引用 std::vector 元素?

哪个向量地址更安全?

C++ 的 std::vector 和线程安全

对 std::vector 元素的赋值是线程安全的吗?

使用 std::vector::swap 方法在 C++ 中交换两个不同的向量是不是安全?

将两个 std::vector<cv::Point> 向量和安全公共点与第三个 std::vector<cv::Point> 进行比较