我如何使用新字符串而不是分配器创建自己的字符串类型向量?
How could I create my own string-type vector with new string instead of allocator?
我写了一个非常简单的代码只是为了看看分配器是否可以为字符串分配内存。它有效。但是,我想知道我是否可以使用 new 关键字实现相同的效果。如果可以的话,哪种方法是更好的做法?我的代码示例如下:
#include<iostream>
#include<string>
#include<memory>
class MyVector{
private:
int size;
int capacity;
std::allocator<std::string> str;
std::string*a;
void allocate(){
capacity = (size-1)*2;
std::string*temp = str.allocate(capacity);
for(int i=0; i<this->getSize();++i){
temp[i] = a[i];
}
str.deallocate(a, 1);
a = temp;
}
public:
MyVector():size(0), capacity(1), a(str.allocate(1)){};
int getSize(){return size;};
int getCapacity(){return capacity;};
void pushBack(std::string input){
++size;
if(this->getSize()>this->getCapacity()){
this->allocate();
}
a[this->getSize()-1]=input;
}
std::string at(int index){
for(int i=0; i<this->getSize();++i){
if(i==index){
return a[i];
}
}
return 0;
}
};
int main(){
MyVector v;
v.pushBack("Sam");
std::cout<<v.at(0)<<std::endl;
return 0;
}
如果我没有正确理解你的问题,你正在尝试动态分配一堆字符串,想知道是否可以使用operator new()
来执行分配,如果可以的话,哪种方法最好。
在回答您的问题之前,我必须指出您的实施存在一些问题。那么让我们来看看吧!
std::string* a
在 MyVector
被销毁时不被释放
这是最明显的一个。没有析构函数,您正在手动管理原始指针,因此必须释放它!现在,当 MyVector 超出范围时,a
指向的内存将变得不可访问,并且不会有垃圾收集器来清理它。
因此我们必须添加这样的方法:
~MyVector() { str.deallocate(a, capacity); }
如果 this->allocate()
无法分配,抛出 std::bad_alloc
当你在做 push_back
?
size
已经递增,容量已经更新,但您尝试推送的字符串尚未复制,缓冲区也没有增长,这使您的容器处于无效状态状态。您可能最终会访问缓冲区边界之外的内存,这是未定义的行为(这可能会工作、崩溃,甚至 get your male cat pregnant)
好的,通过在实际分配发生之后进行分配,可以很容易地解决这个问题。没什么大不了的。对吗?
如果尝试将字符串从旧缓冲区复制到新缓冲区时,其中一个无法分配自己的内部缓冲区怎么办?
嗯,它会在循环中间抛出 std::bad_alloc
。而且您的 temp
缓冲区永远不会被释放。哎哟
事情越来越严重了。我们可以在其中放置一个 catch 子句,但这开始有很多代码只是为了保持指针处于良好状态。
是不是 temp[i] = a[i];
在未初始化的内存上调用赋值运算符,这将是另一个未定义的行为?
我不是 100% 确定这一点,但我的 C++ 直觉告诉我这是相当冒险的。
那么,我该如何摆脱这一长串问题呢?
改用new
?也许 new[]
因为这是一个字符串数组?
好吧,这会更干净,尤其是如果您无论如何都要使用默认值 std::allocator
。
等等,还有更好的!
正在查看 C++ Core Guidelines, we can see under P.8 that we shouldn't leak any resources, it is advised to use RAII, and to look for "naked new". Basically, what that means is that you should avoid using new in normal code to allocate resources dynamically. Instead, the guidelines encourage you to use unique_ptr and to use make_unique() to construct objects owned by unique_ptrs
作为参考,这里是unique_ptr
's page on cppreference. You can also read a bit more about it here, or watch one of the designers of the language explain the concepts I touched on much better than I could on YouTube
遵循这些准则,您的代码可能会变得更加符合现代规范。它看起来像这样:
#include<string>
#include<memory>
class MyVector{
private:
int size;
int capacity;
std::unique_ptr<std::string[]> a;
void allocate(){
size_t new_capacity = size*2;
auto temp = std::make_unique<std::string[]>(new_capacity);
std::copy(a.get(), a.get()+size, temp.get());
capacity = new_capacity; // We have finished all the operations that could throw!
std::swap(a, temp); // Because this can't throw
}
public:
MyVector():size(0), capacity(1) {}
// Since unique_ptr<>'s destructor is called automatically
// we don't need to do it explicitely!
int getSize(){return size;};
int getCapacity(){return capacity;};
void pushBack(std::string input){
if(this->getSize() == this->getCapacity()){ // We have to change the comparison
this->allocate();
}
a[this->getSize()] = input; // This could throw too!
++size;
}
std::string at(int index){
if(index >= size)
throw std::out_of_range("Trying to access an element past the end in MyVector!");
return a.get()[index];
}
};
最后一个音符
这个容器仍然很低效(2 的增长因子不是理论上最好的,虽然我对它了解不多),它没有移动语义的概念,它不能被复制, 它不能专门用于其他类型(尽管这不会太困难),它没有方便的迭代器来与算法或基于范围的 for 循环一起使用,等等。
然而,这是一个非常好的学习练习,我赞赏你通过在 Whosebug 上发布你的结果来尝试改进它:)
制作一个生产就绪的容器实际上需要很多工作,并且需要相当深的硬件知识才能正确使用,所以作为结论,我建议您在实际需要时坚持使用 std::vector在某处使用矢量 ;)
我写了一个非常简单的代码只是为了看看分配器是否可以为字符串分配内存。它有效。但是,我想知道我是否可以使用 new 关键字实现相同的效果。如果可以的话,哪种方法是更好的做法?我的代码示例如下:
#include<iostream>
#include<string>
#include<memory>
class MyVector{
private:
int size;
int capacity;
std::allocator<std::string> str;
std::string*a;
void allocate(){
capacity = (size-1)*2;
std::string*temp = str.allocate(capacity);
for(int i=0; i<this->getSize();++i){
temp[i] = a[i];
}
str.deallocate(a, 1);
a = temp;
}
public:
MyVector():size(0), capacity(1), a(str.allocate(1)){};
int getSize(){return size;};
int getCapacity(){return capacity;};
void pushBack(std::string input){
++size;
if(this->getSize()>this->getCapacity()){
this->allocate();
}
a[this->getSize()-1]=input;
}
std::string at(int index){
for(int i=0; i<this->getSize();++i){
if(i==index){
return a[i];
}
}
return 0;
}
};
int main(){
MyVector v;
v.pushBack("Sam");
std::cout<<v.at(0)<<std::endl;
return 0;
}
如果我没有正确理解你的问题,你正在尝试动态分配一堆字符串,想知道是否可以使用operator new()
来执行分配,如果可以的话,哪种方法最好。
在回答您的问题之前,我必须指出您的实施存在一些问题。那么让我们来看看吧!
std::string* a
在MyVector
被销毁时不被释放这是最明显的一个。没有析构函数,您正在手动管理原始指针,因此必须释放它!现在,当 MyVector 超出范围时,
a
指向的内存将变得不可访问,并且不会有垃圾收集器来清理它。因此我们必须添加这样的方法:
~MyVector() { str.deallocate(a, capacity); }
如果
this->allocate()
无法分配,抛出std::bad_alloc
当你在做push_back
?size
已经递增,容量已经更新,但您尝试推送的字符串尚未复制,缓冲区也没有增长,这使您的容器处于无效状态状态。您可能最终会访问缓冲区边界之外的内存,这是未定义的行为(这可能会工作、崩溃,甚至 get your male cat pregnant)好的,通过在实际分配发生之后进行分配,可以很容易地解决这个问题。没什么大不了的。对吗?
如果尝试将字符串从旧缓冲区复制到新缓冲区时,其中一个无法分配自己的内部缓冲区怎么办?
嗯,它会在循环中间抛出
std::bad_alloc
。而且您的temp
缓冲区永远不会被释放。哎哟事情越来越严重了。我们可以在其中放置一个 catch 子句,但这开始有很多代码只是为了保持指针处于良好状态。
是不是
temp[i] = a[i];
在未初始化的内存上调用赋值运算符,这将是另一个未定义的行为?我不是 100% 确定这一点,但我的 C++ 直觉告诉我这是相当冒险的。
那么,我该如何摆脱这一长串问题呢?
改用new
?也许 new[]
因为这是一个字符串数组?
好吧,这会更干净,尤其是如果您无论如何都要使用默认值 std::allocator
。
等等,还有更好的!
正在查看 C++ Core Guidelines, we can see under P.8 that we shouldn't leak any resources, it is advised to use RAII, and to look for "naked new". Basically, what that means is that you should avoid using new in normal code to allocate resources dynamically. Instead, the guidelines encourage you to use unique_ptr and to use make_unique() to construct objects owned by unique_ptrs
作为参考,这里是unique_ptr
's page on cppreference. You can also read a bit more about it here, or watch one of the designers of the language explain the concepts I touched on much better than I could on YouTube
遵循这些准则,您的代码可能会变得更加符合现代规范。它看起来像这样:
#include<string>
#include<memory>
class MyVector{
private:
int size;
int capacity;
std::unique_ptr<std::string[]> a;
void allocate(){
size_t new_capacity = size*2;
auto temp = std::make_unique<std::string[]>(new_capacity);
std::copy(a.get(), a.get()+size, temp.get());
capacity = new_capacity; // We have finished all the operations that could throw!
std::swap(a, temp); // Because this can't throw
}
public:
MyVector():size(0), capacity(1) {}
// Since unique_ptr<>'s destructor is called automatically
// we don't need to do it explicitely!
int getSize(){return size;};
int getCapacity(){return capacity;};
void pushBack(std::string input){
if(this->getSize() == this->getCapacity()){ // We have to change the comparison
this->allocate();
}
a[this->getSize()] = input; // This could throw too!
++size;
}
std::string at(int index){
if(index >= size)
throw std::out_of_range("Trying to access an element past the end in MyVector!");
return a.get()[index];
}
};
最后一个音符
这个容器仍然很低效(2 的增长因子不是理论上最好的,虽然我对它了解不多),它没有移动语义的概念,它不能被复制, 它不能专门用于其他类型(尽管这不会太困难),它没有方便的迭代器来与算法或基于范围的 for 循环一起使用,等等。
然而,这是一个非常好的学习练习,我赞赏你通过在 Whosebug 上发布你的结果来尝试改进它:)
制作一个生产就绪的容器实际上需要很多工作,并且需要相当深的硬件知识才能正确使用,所以作为结论,我建议您在实际需要时坚持使用 std::vector在某处使用矢量 ;)