这里主要介绍下reserce/resize、push_back/emplace_back、shrink_to_fit/clear等接口;
1. reserve and resize
C++的vector对象可以通过reserve方法来设置vector对象的容量,通过resize方法来改变vector对象的大小。reserve所设置的容量指的是vector容器中可容纳元素的预留空间个数,但并不真正创建元素对象。resize则是直接改变vector容器中元素的个数,并且创建对象或者销毁空间。
resize()函数和容器的size息息相关。调用resize(n)后,容器的size即为n。至于是否影响capacity,取决于调整后的容器的size是否大于capacity。
reserve()函数和容器的capacity息息相关。调用reserve(n)后,若容器的capacity<n,则重新分配内存空间,从而使得capacity等于n。
如果capacity>=n呢?capacity无变化。
从两个函数的用途可以发现,容器调用resize()函数后,所有的空间都已经初始化了,所以可以直接访问。
而reserve()函数预分配出的空间没有被初始化,所以不可访问。
#include <iostream>
#include <vector>
#include <string>
#include <map>using ValueType = std::string;
const ValueType dv = "aaaaaaaaaaaaaaaaaaaaaaaa";
constexpr int maxSize = 10;class Widget {
public:Widget(ValueType value = dv) : value{value}{std::cout << "Widget(ValueType) constructor: " << this << std::endl;}Widget(const Widget& w) : value{w.value}{std::cout<<"copy constructor" << std::endl;}Widget& operator=(const Widget& rhs){if (this != &rhs){value = rhs.value;//assign new resource}std::cout << "copy assignment constructor" << std::endl;return *this;}//这里移动构造函数定义为noexceptWidget(Widget&& w) noexcept: value{std::move(w.value)}{std::cout << "move constructor" << std::endl;}Widget& operator=(Widget&& rhs){if (this != &rhs){value = std::move(rhs.value);//assign new resource}std::cout << "move assignment constructor" << std::endl;return *this;}~Widget(){if (value.empty()){std::cout << "0~destructor:" << this << std::endl;}else{value.clear();std::cout << "~destructor:" << this << std::endl;}}void print(){std::cout << "value:" << value << " : " << this << std::endl;}
private:ValueType value{};
};void test_reserve()
{std::vector<Widget> v{Widget{"aaaaaaaaaaaaaaaaaaaaaaaa"}, Widget{"bbbbbbbbbbbbbbbbbbbbbbbb"}, Widget{"ccccccccccccccccccccccc"}};std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;v.reserve(2);//当reserve的数据空间小于capacity的时候什么都不做;std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;//当reserve的数据空间大于capacity时,vector的capacity变为reserve指定的大校v.reserve(5);//由于要重新分配比 3 更大的存储空间,这里再次调用的移动构造函数(如果移动构造函数没有noexcept,则调用拷贝构造函数)std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;v.reserve(4);std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;v.reserve(2);std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
}void test_resize()
{std::vector<Widget> v{Widget{"aaaaaaaaaaaaaaaaaaaaaaaa"}, Widget{"bbbbbbbbbbbbbbbbbbbbbbbb"}, Widget{"ccccccccccccccccccccccc"}};std::cout << std::endl;std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;v.resize(5);//vector重新分配空间(需要移动/拷贝原数据),resize的空间大于size()时,会构造 5 - size()个数据项(这里是构造2个新的Widget)std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;v.resize(4);//destory多余指定size的数据项std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;v.resize(2);//destory多余指定size的数据项std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
}int main()
{test_reserve();std::cout << "----------------------" << std::endl;test_resize();return 0;
}
output:
2. vector(count, value) vs reserve(n)
这里有一个点需要注意下:vector( size_type count,
const T& value,
const Allocator& alloc = Allocator() );
和 vector.reserve的区别, 这个vector会构造数据项,而reserve不构造数据项,只是预留了空间;
#include <iostream>
#include <vector>void test_vector_constructor_vs_reserve()
{std::vector<int> v1(2,3);std::cout << "size()=" << v1.size() << ", capacity()=" << v1.capacity() << std::endl << std::endl;for(const auto i : v1){std::cout << i << ' ';}std::cout << std::endl;std::vector<int> v2{};v2.reserve(2);//当reserve的数据空间小于capacity的时候什么都不做;std::cout << "size()=" << v2.size() << ", capacity()=" << v2.capacity() << std::endl << std::endl;
}int main()
{test_vector_constructor_vs_reserve();return 0;
}
3. shrink_to_fit vs clear
shrink_to_fit() : 它减少容器的容量以适应其大小并销毁超出容量的所有元素。
clear():从容器擦除所有元素。此调用后 size() 返回零。使任何指代所含元素的引用、指针或迭代器失效。任何尾后迭代器也会失效。 保持 vector
的 capacity() 不变。
#include <iostream>
#include <vector>
#include <string>using ValueType = std::string;
const ValueType dv = "aaaaaaaaaaaaaaaaaaaaaaaa";
constexpr int maxSize = 10;class Widget {
public:Widget(ValueType value = dv) : value{value}{std::cout << "Widget(ValueType) constructor: " << this << std::endl;}Widget(const Widget& w) : value{w.value}{std::cout<<"copy constructor" << std::endl;}Widget& operator=(const Widget& rhs){if (this != &rhs){value = rhs.value;//assign new resource}std::cout << "copy assignment constructor" << std::endl;return *this;}//这里移动构造函数定义为noexceptWidget(Widget&& w) noexcept: value{std::move(w.value)}{std::cout << "move constructor" << std::endl;}Widget& operator=(Widget&& rhs){if (this != &rhs){value = std::move(rhs.value);//assign new resource}std::cout << "move assignment constructor" << std::endl;return *this;}~Widget(){if (value.empty()){std::cout << "0~destructor:" << this << std::endl;}else{value.clear();std::cout << "~destructor:" << this << std::endl;}}void print(){std::cout << "value:" << value << " : " << this << std::endl;}
private:ValueType value{};
};void test_shrink_to_fit()
{std::vector<Widget> v;std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;v.emplace_back(std::string(24, 'a'));v.emplace_back(std::string(24, 'b'));v.emplace_back(std::string(24, 'c'));v.emplace_back(std::string(24, 'd'));v.emplace_back(std::string(24, 'e'));std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;v.shrink_to_fit();//按照我测试结果,测试capacity=8, size为添加的5个元素;//shrink_to_fit会释放掉多余的vector空间,使得capacity()==size();//这里可能会重新分配空间,具体依赖于编译器的实现std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;
}void test_clear()
{std::vector<Widget> v{Widget{"aaaaaaaaaaaaaaaaaaaaaaaa"}, Widget{"bbbbbbbbbbbbbbbbbbbbbbbb"}, Widget{"ccccccccccccccccccccccc"}};std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;std::cout << "call clear():" << std::endl;;v.clear();//会把所有数据项destory掉,此时size()大小为0, capacity则不变;std::cout << "size()=" << v.size() << ", capacity()=" << v.capacity() << std::endl << std::endl;}int main()
{test_shrink_to_fit();std::cout << std::endl << "----------------------" << std::endl << std::endl;test_clear();return 0;
}
4. push_back vs emplace_back
push_back:是插入数据到vector,这里会构造一个临时的数据项,这个临时数据项参数满足push_back(T&& value)接口,通过移动构造函数把这个临时的数据项移动到vector对应的位置上;
emplace_back:是通过完美转发参数,在vector对应的位置上就地构造,只需要调用一次构造函数就可以;这种情况下,效率上来说要比push_back高一些;
对于push_back和emplace_back来说,如果传入的数据项本身是个左值,那么他们效率上都是一样的。因为这两个接口都只需要调用一次拷贝构造函数。代码如下:
#include <iostream>
#include <vector>
#include <string>
#include <map>using ValueType = std::string;
const ValueType dv = "aaaaaaaaaaaaaaaaaaaaaaaa";
constexpr int maxSize = 10;class Widget {
public:Widget(ValueType value = dv) : value{value}{std::cout << "Widget(ValueType) constructor: " << this << std::endl;}Widget(const Widget& w) : value{w.value}{std::cout<<"copy constructor" << std::endl;}Widget& operator=(const Widget& rhs){if (this != &rhs){value = rhs.value;//assign new resource}std::cout << "copy assignment constructor" << std::endl;return *this;}Widget(Widget&& w) : value{std::move(w.value)}{std::cout << "move constructor" << std::endl;}Widget& operator=(Widget&& rhs){if (this != &rhs){value = std::move(rhs.value);//assign new resource}std::cout << "move assignment constructor" << std::endl;return *this;}~Widget(){if (value.empty()){std::cout << "0~destructor:" << this << std::endl;}else{value.clear();std::cout << "~destructor:" << this << std::endl;}}void print(){std::cout << "value:" << value << " : " << this << std::endl;}
private:ValueType value{};
};
void push_vs_empalce_with_rvalue_item()
{std::vector<Widget> v{};v.reserve(10);std::cout << std::endl;std::string str(24, 'a');v.push_back(str);std::cout << std::endl;v.emplace_back(str);std::cout << std::endl;}
void push_vs_empalce_with_lvalue_item()
{Widget w1("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");Widget w2("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");std::vector<Widget> v{};v.reserve(10);std::cout << std::endl;v.push_back(w1);std::cout << std::endl;v.emplace_back(w2);std::cout << std::endl;
}int main()
{push_vs_empalce_with_rvalue_item();std::cout << std::endl << "-------------------------" << std::endl << std::endl;push_vs_empalce_with_lvalue_item();return 0;
}
output:
后续再介绍vector插入数据的性能等;