ptr_vector與vector+unique_ptr 區別

引言

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

發佈了111 篇原創文章 · 獲贊 69 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章