代码之家  ›  专栏  ›  技术社区  ›  Oliv

在没有未定义行为的情况下实现类似于std::vector的容器

  •  2
  • Oliv  · 技术社区  · 6 年前

    它可能会让一些编码人员感到惊讶,而且,尽管它可能令人惊讶,但它不可能实现 std::vector 没有编译器的非标准支持。问题本质上在于对原始存储区域执行指针算法的能力。报纸, p0593: Implicit creation of objects for low-level object manipulation 出现在@ShafikYaghmour答案中,清楚地暴露了问题,并建议对标准进行修改,以使类向量容器和其他法律级编程技术的实现更容易。

    不过,我想知道是否没有解决方法来实现与 标准::向量 只使用语言提供的内容而不使用标准库。

    目标是在原始存储区域中逐个构造向量元素,并能够使用迭代器访问这些元素。这相当于在std::vector上的一系列push_。

    为了了解这个问题,下面是对 标准::向量 在libc++或libstdc++中:

    void access_value(std::string x);
    
    std::string s1, s2, s3;
    //allocation
    auto p=static_cast<std::string*>(::operator new(10*sizeof(std::string)));
    
    //push_back s1
    new(p) std::string(s1);
    access_value(*p);//undefined behavior, p is not a pointer to object
    
    //push_back s2
    new(p+1) std::string(s2);//undefined behavior
            //, pointer arithmetic but no array (neither implicit array of size 1)
    access_value(*(p+1));//undefined behavior, p+1 is not a pointer to object
    
    //push_back s2
    new(p+2) std::string(s3);//undefined behavior
            //, pointer arithmetic but no array
    access_value(*(p+2));//undefined behavior, p+2 is not a pointer to object
    

    //almost trivialy default constructible
    template<class T>
    union atdc{
      char _c;
      T value;
      atdc ()noexcept{ }
      ~atdc(){}
    };
    

    原始存储将使用此联合类型的数组进行初始化,指针算法始终在此数组上执行。然后在每次推回时,在联合的非活动成员上构造元素。

    std::string s1, s2, s3;
    auto p=::operator new(10*sizeof(std::string));
    auto arr = new(p) atdc<std::string>[10];
    //pointer arithmetic on arr is allowed
    
    //push_back s1
    new(&arr[0].value) std::string(s1); //union member activation
    access_value(arr[0].value);
    
    //push_back s2
    new(&arr[1].value) std::string(s2);
    access_value(arr[1].value);
    
    //push_back s2
    new(&arr[2].value) std::string(s2);
    access_value(arr[2].value);
    

    上面的代码中是否有未定义的行为?

    1 回复  |  直到 6 年前
        1
  •  7
  •   Shafik Yaghmour    6 年前

    这是一个正在积极讨论的话题,我们可以在提案中看到这一点 p0593: Implicit creation of objects for low-level object manipulation

    包括以下讨论:

    2.3. 阵列的动态构造

    ....

    实现,但根据C++对象模型,未定义 行为发生在点#a、#b、#c、#d和#e,因为它们试图

    在位置#b、#c和#d,对char*执行算术, 理想情况下,这个问题的解决方案将使这两种计算都具有 定义的行为。

    使用 模数转换器 T* i、 e.通过 std::vector::data T型* strict aliasing rules