实现一个没有未定义行为的std :: vector like容器

Oli*_*liv 2 c++ memory-management undefined-behavior language-lawyer

它可能会让一些程序员感到惊讶,并且尽管可能令人惊讶,但如果没有std::vector编译器的非标准支持,则无法实现.问题基本上在于能够在原始存储区域上执行指针运算.论文,p0593:出现在@ShafikYaghmour答案中的低级对象操纵对象的隐式创建,清楚地揭示了问题,并提出修改标准,以便更容易实现像容器和其他法律级编程技术的矢量.

然而,我想知道是否没有工作来实现一个类型,std::vector只相当于使用语言提供的内容而不使用标准库.

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

为了解问题,请简单介绍std::vector在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
Run Code Online (Sandbox Code Playgroud)

我的想法是使用一个永远不会初始化其成员的联合.

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

原始存储将使用此联合类型的数组进行初始化,并且始终在此数组上执行指针运算.然后在每个push_back的union的非活动成员上构造元素.

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);
Run Code Online (Sandbox Code Playgroud)

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

Sha*_*our 6

这是一个正在积极讨论的主题,我们可以在提案p0593中看到这一点:为低级对象操作隐式创建对象.这是对问题的非常可靠的讨论以及为什么在没有变化的情况下它们无法修复.如果您对所考虑的方法有不同的方法或强烈的观点,您可能希望与提案作者联系.

它包括这个讨论:

2.3.数组的动态构造

考虑一下这个程序试图实现类似std :: vector的类型(为简洁起见省略了许多细节):

....

在实践中,此代码适用于一系列现有实现,但根据C++对象模型,未定义的行为发生在点#a,#b,#c,#d和#e,因为它们尝试执行指针算法分配的存储区域,不包含数组对象.

在位置#b,#c和#d处,对char*执行算术运算,并且在位置#a,#e和#f处,对T*执行算术运算.理想情况下,这个问题的解决方案会使计算与定义的行为一致.

  1. 途径

上面的代码片段有一个共同的主题:他们尝试使用他们从未创建过的对象.实际上,有一系列类型,程序员认为他们不需要显式创建对象.我们建议识别这些类型,并仔细划分出不需要显式创建此类对象的规则,而是隐式地创建它们.

使用adc union 的方法有一个问题,我们希望能够通过指针访问包含的数据,T*即通过std :: vector :: data.以一个方式访问union T*会违反严格的别名规则,因此是未定义的行为.

  • ...分为两类方言,一类不适合涉及低级编程的目的,另一类仅支持有限的优化。根据 C Rationale,C 精神包括“不要阻止程序员做需要做的事情”的原则。基于程序员不会做 X 的想法进行优化仅在程序员不需要做 X 的情况下才有帮助。不幸的是,大多数关于定义行为的争论完全忽略了这一点。 (2认同)