在没有未定义行为的情况下实现类似std::vector的容器
Implementing a std::vector like container without undefined behavior
这可能会让一些程序员感到惊讶,尽管令人惊讶,但如果没有编译器的非标准支持,就不可能实现std::vector
。问题本质上在于对原始存储区域执行指针运算的能力。文章p0593:ShafikYaghmour答案中出现的用于低级对象操作的对象的隐式创建,清楚地暴露了问题,并提出了对标准的修改,以使类向量容器和其他法律级编程技术的实现更容易。
尽管如此,我想知道是否没有办法只使用该语言提供的内容而不使用任何标准库来实现等效于std::vector
的类型。
目标是在原始存储区域中一个接一个地构造矢量元素,并能够使用迭代器访问这些元素。这相当于std::向量上的push_back序列。
为了了解这个问题,请简化在libc++或libstdc++中对std::vector
的实现执行的操作:
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(){}
};
原始存储将使用此联合类型的数组进行初始化,并且指针运算始终在此数组上执行。然后,在每次push_back时,在并集的非活动成员上构造元素。
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);
上面的代码中有没有未定义的行为?
这是一个正在积极讨论的主题,我们可以在提案p0593中看到这一点:为低级对象操作隐式创建对象。这是对这些问题的一次相当扎实的讨论,以及为什么不改变这些问题就无法解决。如果你对正在考虑的方法有不同的方法或强烈的看法,你可能想联系提案作者。
其中包括以下讨论:
2.3.阵列的动态构建
考虑一下这个程序,它试图实现一个类似std::vector的类型(为了简洁起见,省略了许多细节(:
在实践中,此代码适用于一系列现有的实现,但根据C++对象模型,未定义行为发生在点#a、#b、#c、#d和#e,因为它们尝试对分配的存储区域执行指针运算不包含数组对象。
在位置#b、#c和#d,对字符*执行算术,并且在位置#a、#e和#f处,对T*执行运算。理想情况下,这个问题的解决方案会给两个计算注入定义的行为。
- 方法
上面的代码段有一个共同的主题:它们试图使用从未创建过的对象。事实上,有一个类型家族,程序员认为他们不需要显式地创建对象。我们建议识别这些类型,并仔细制定规则,通过隐式创建来消除显式创建此类对象的必要性。
使用adc并集的方法存在问题,我们希望能够通过指针T*
访问包含的数据,即通过std::vector::data。作为T*
访问并集将违反严格的别名规则,因此是未定义的行为。
- 使用std::vector的OpenCL矩阵乘法
- POCO::PostgreSQL:如何将std::vector支持添加到`Binder::bind`
- std::vector的包装器,使数组的结构看起来像结构的数组
- 编译器如何区分std::vector的构造函数
- 使用 pqxx 将 std::vector 存储在 postgresql 中,并从数据库中检索它
- 在std::vector上存储带有模板的类实例
- 在main()之外初始化std::vector会导致性能下降(多线程)
- 为什么std::vector比数组慢
- std::vector::迭代器是否可以合法地作为指针
- 如何将二进制格式的 C++ 对象的 std::vector 保存到磁盘?
- 为什么std::vector和std::valarray初始化构造函数不同
- ";结果类型必须是可从输入范围的值类型""构造的;创建std::vector时
- 在没有未定义行为的情况下实现类似std::vector的容器
- 如何调整 std::vector of Eigen::MatrixXd 的大小
- 使用 std::vector::reverse_iterator 将 int 序列化为字节向量?
- 如何将AERT_Allocate与 std:vector 一起使用
- 推导 std::vector::back() 的返回类型
- 如何将原始字节附加到 std::vector?
- std::vector 没有重载函数的实例与参数列表匹配
- 如果 KEY 是 std::list 或 std::vector 而不是值,那么 std::map 的默认行为是什么?