引言
boost库中的ptr_container给予我们了一种管理指针的通用方法,而在C++11后标准库引入了std::unique_ptr以后vector< unique_ptr>的做法也可以让我们实现看上去差别不大的功能,那么它们之间的区别是什么呢,我们从概念和效率两个方面来谈谈这个问题.
概念
首先ptr_vector是属于ptr_container的这个大类中的,意味着作用是把在堆分配的对象的指针存储在容器中,进行自动管理,在容器对象析构的时候进行释放,这也就是其中不能存储一个栈上对象取址的指针或者nullptr的原因.两者之间最大的区别就是vector<unique_ptr>可以在存储多态对象的情况下正常使用标准库的泛型算法,而ptr_vector在使用这些泛型算法时迭代器不会返回内部存储的指针,所以在使用标准泛型算法是算法会尝试拷贝指针指向的对象,这在存储为多态对象时会出现问题,下面看一段代码来解释这个问题(测试代码来自stackoverflow)
#include <vector>
#include <memory>
#include <iostream>
#include <algorithm>
class Animal
{
public:
Animal() = default;
Animal(const Animal&) = delete;
Animal& operator=(const Animal&) = delete;
virtual ~Animal() = default;
virtual void speak() const = 0;
};
class Cat
: public Animal
{
public:
virtual void speak() const {std::cout << "Meow\n";}
virtual ~Cat() {std::cout << "destruct Cat\n";}
};
class Dog
: public Animal
{
public:
virtual void speak() const {std::cout << "Bark\n";}
virtual ~Dog() {std::cout << "destruct Dog\n";}
};
class Sheep
: public Animal
{
public:
virtual void speak() const {std::cout << "Baa\n";}
virtual ~Sheep() {std::cout << "destruct Sheep\n";}
};
int main()
{
typedef std::unique_ptr<Animal> Ptr;
std::vector<Ptr> v;
v.push_back(Ptr(new Cat));
v.push_back(Ptr(new Sheep));
v.push_back(Ptr(new Dog));
v.push_back(Ptr(new Sheep));
v.push_back(Ptr(new Cat));
v.push_back(Ptr(new Dog));
for (auto const& p : v)
p->speak();
std::cout << "Remove all sheep\n";
v.erase(
std::remove_if(v.begin(), v.end(),
[](Ptr& p)
{return dynamic_cast<Sheep*>(p.get());}),
v.end());
for (auto const& p : v)
p->speak();
}
我们对这段代码的预期就是删除掉所有的sheep
Meow
Baa
Bark
Baa
Meow
Bark
Remove all sheep
destruct Sheep
destruct Sheep
Meow
Bark
Meow
Bark
destruct Cat
destruct Dog
destruct Cat
destruct Dog
完全符合预期,但如果使用ptr_vector呢
boost::ptr_vector<Animal> v;
v.push_back(new Cat);
v.push_back(new Sheep);
v.push_back(new Dog);
v.push_back(new Sheep);
v.push_back(new Cat);
v.push_back(new Dog);
v.push_back(new Sheep);
v.push_back(new Sheep);
for (auto const& p : v)
p.speak();
std::cout << "Remove all sheep\n";
v.erase(
std::remove_if(v.begin(), v.end(),
[](Animal& p)
{return dynamic_cast<Sheep*>(&p);}),
v.end());
for (auto const& p : v)
p.speak();
我们会发现编译错误,原因是使用的删除的功能,这也印证了文章开头,我们注释掉删除拷贝功能的那一行,
class Animal
{
public:
Animal() = default;
//Animal(const Animal&) = delete;
//Animal& operator=(const Animal&) = delete;
virtual ~Animal() = default;
virtual void speak() const = 0;
};
然后进行测试 我们会发现结果完全不符合预期
Meow
Baa
Bark
Baa
Meow
Bark
Baa
Baa
Remove all sheep
destruct Cat
destruct Dog
destruct Sheep
destruct Sheep
Meow
Baa
Bark
Baa
destruct Cat
destruct Sheep
destruct Dog
destruct Sheep
正是因为这样的差异使得其内部提供了不少类似的函数
v.erase_if([](Animal& p)
{return dynamic_cast<Sheep*>(&p);});
其实这样看来如果你仅仅想去管理一个正常的指针,不涉及多态,其实两者没有什么不同,就算你想对其中的元素进行一些操作,正确的写法同样可以抹平差异,而std版本在这种博弈中显然有一个显著的优势,即使你的代码更加轻便,使用ptr_vector意味着要是你的项目载入boost库,如果你在项目中并没有使用什么boost的内容,那何必这样呢.
效率
下面是测试代码
#include <vector>
#include <memory>
#include <chrono>
#include <iostream>
#include <algorithm>
#include <boost/ptr_container/ptr_vector.hpp>
class item{
private:
int value;
public:
explicit item(int para) : value(para){}
friend bool operator<(const item& lhs, int rhs){
return lhs.value < rhs;
}
};
boost::ptr_vector<item> pointerContainer;
std::vector<std::unique_ptr<item>> container;
std::vector<std::shared_ptr<item>> container_;
int main(){
const int count = 10000000;
auto start = std::chrono::high_resolution_clock::now();
for(size_t i = 0; i < count; i++)
{
//pointerContainer.push_back(new item(i));
//container.push_back(std::make_unique<item>(i));
//container_.push_back(std::make_shared<item>(i));
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::ratio<1,100000>> time_span
= std::chrono::duration_cast<std::chrono::duration<double, std::ratio<1,100000>>>(end - start);
std::cout << time_span.count() << std::endl;
return 0;
}
container | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
ptr_vector | 100231 | 101990 | 104459 | 102521 | 115296 |
vector< std::shared_ptr> | 307599 | 306587 | 306477 | 306998 | 306663 |
vector< std::unique_ptr> | 390737 | 392270 | 391649 | 391554 | 434274 |
这样的结果令人吃惊的,但是ptr_vector这样看来确实是最快,同样的数据ptr_vector一秒就可以完成,而vector的两个版本要分别花费三秒和四秒,
再来对remove_if和erase_if做一个测试
boost::ptr_vector<item> pointerContainer;
typedef std::unique_ptr<item> con;
std::vector<con> container;
typedef std::shared_ptr<item> con_;
std::vector<con_> container_;
int main(){
const int count = 10000000;
for(size_t i = 0; i < count; i++)
{
//pointerContainer.push_back(new item(i));
//container.push_back(std::make_unique<item>(i));
container_.push_back(std::make_shared<item>(i));
}
auto start = std::chrono::high_resolution_clock::now();
/* pointerContainer.erase_if([](item& para){
return para < 5000000;
}); */
std::remove_if(container_.begin(), container_.end(), [](con_& para){
return *para < 5000000;
});
/* std::remove_if(container.begin(), container.end(), [](con& para){
return *(para.get()) < 5000000;
}); */
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::ratio<1,100000>> time_span
= std::chrono::duration_cast<std::chrono::duration<double, std::ratio<1,100000>>>(end - start);
std::cout << time_span.count() << std::endl;
}
container | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
ptr_vector | 26008.7 | 26381.2 | 25986.3 | 26028 | 26315.6 |
vector< std::shared_ptr> | 69001.5 | 84720.8 | 68968.8 | 68967.7 | 68986.1 |
vector< std::unique_ptr> | 107720 | 100603 | 98902.1 | 99598.2 | 103429 |
O(n)的算法,性能与内存分配的效率比差不多,基本是1:3:4左右
结果
经过以上的分析我认为这两个的版本的使用要从我们的需求出发,功能上其实差不了多少,效率虽说有差距,但并不是量级,在数据较小时完全可以忽略,基本就是一万的数据量各项有十毫秒左右的差异,这带来的代价是使用在项目中载入boost库,孰轻孰重,全看个人.
参考:
https://mine260309.me/archives/1290
https://blog.csdn.net/rain_qingtian/article/details/11385519
https://stackoverflow.com/questions/9469968/stl-container-with-stdunique-ptrs-vs-boostptr-container
https://stackoverflow.com/questions/23872162/should-we-prefer-vectorunique-ptr-or-boostptr-vector