2016-09-01 87 views
0

我正在創建一個使用unique_ptr的數據結構。我現在想要在這個數據結構上定義不同的迭代器,但是我的數據結構的節點是數據本身的一部分。正因爲如此,我希望迭代器返回實際節點,而不僅僅返回其中包含的值。使用unique_ptr實現的列表的迭代器

這是我走到這一步,(更加簡化的例子):

#include <algorithm> 
#include <iostream> 
#include <memory> 

using namespace std; 

template <typename T> struct node { 
    node(T val) : val(val), next(nullptr) {} 
    node(T val, unique_ptr<node<T>> &n) : val(val), next(move(n)) {} 
    T val; 
    unique_ptr<node<T>> next; 

    template <bool Const = true> struct iter { 
    using reference = 
     typename std::conditional<Const, const node<T> *, node<T> *>::type; 
    iter() : nptr(nullptr) {} 
    iter(node<T> *n) : nptr(n) {} 

    reference operator*() { return nptr; } 

    iter &operator++() { 
     nptr = nptr->next.get(); 
     return *this; 
    } 

    friend bool operator==(const iter &lhs, const iter &rhs) { 
     return lhs.nptr == rhs.nptr; 
    } 

    friend bool operator!=(const iter &lhs, const iter &rhs) { 
     return lhs.nptr != rhs.nptr; 
    } 
    node<T> *nptr; 
    }; 

    iter<> begin() const { return iter<>(this); } 
    iter<> end() const { return iter<>(); } 

    iter<false> begin() { return iter<false>(this); } 
    iter<false> end() { return iter<false>(); } 
}; 

template <typename T> void pretty_print(const unique_ptr<node<T>> &l) { 
    auto it = l->begin(); 
    while (it != l->end()) { 
    auto elem = *it; 
    cout << elem->val << endl; 
    ++it; 
    } 
} 

int main() { 
    auto a = make_unique<node<int>>(4); 
    auto b = make_unique<node<int>>(3, a); 
    auto c = make_unique<node<int>>(2, b); 
    auto d = make_unique<node<int>>(1, c); 

    for (auto *elem : *d) { 
    elem->val = elem->val - 1; 
    } 

    pretty_print(d); 

    return 0; 
} 

是它認爲不好的做法,暴露原始指針的數據結構的這樣的元素呢?這會在更復雜的例子中起作用,特別是在const-正確性方面?

回答

3

這是很大的意見,但我會說這是一個壞主意。 unique_ptr s應該是唯一的;罕見的例外情況應該是如果你需要將一個原始指針傳遞給其他沒有正確模板化的函數(並且你知道它不能保持指針)。

否則,您處於合理使用狀態,例如使用迭代器初始化爲std::vector<node<int>*>,違反了烘焙到unique_ptr的假設。一般而言,您希望您的API在有限的開發人員頭痛的情況下可預測地行爲,並且您的建議增加了令人頭痛的問題:「只要不存儲任何超過我自定義結構壽命的任何事物的任何東西,您就可以迭代它」。

更好的選擇是:

  1. 返回到您的實際unique_ptr S變量(這樣的人能夠在不違反合同的獨特性與他們一起工作,讓他們const如果他們不應該被突變);他們必須採取所有權或明確使用「借來的」非託管指針來冒險來存儲結果,但迭代會正常工作,並且他們不會意外違反唯一性保證
  2. 在內部存儲shared_ptr s,併發出新的迭代過程中shared_ptr S,所以所有權自動共享和壽命長延伸爲一個共享指針仍

重要的是,無論這兩個選項允許某人意外「做錯事」。調用者可以做壞事,但他們必須明確地繞過智能指針保護機制來做到這一點,而這正是他們的頭。

當然,第三個選項是:

  • 返回值的引用指向,而不是指針;如果該值存儲應該是拷貝構造
  • 這是用戶能夠獲得這最後一個錯誤(通過存儲的參考長遠來看,考慮它的地址,以獲得新的指針違反唯一性保證等),但它遵循現有的C++公約(正如Ryan在評論中指出的)容器如std::vector,所以它不是一個新的問題; C++開發人員通常不會對從迭代中獲取的引用做出糟糕的事情。 它可以說是最好的選擇,因爲它可以很好地映射到標準模式,使開發人員通過適應現有的心智模型更容易。

    +0

    'std :: vector '也維護其元素的唯一所有權,但是每次使用'operator []'或iterate時,都會獲得對它們的引用。 [非擁有指針和引用罰款](https://www.youtube.com/watch?v=xnqTKD8uD64#t=14m48s) –

    +0

    沒有任何錯誤通過非擁有指針作爲原始指針。標準庫,即使添加了'std :: observer_ptr',它基本上是一個n對象中的原始指針。傳遞周圍唯一指針的引用非常糟糕。通過引用它的內容。 –

    +0

    @瑞安海寧:好點。我明確表示這也是一個合理的選擇。 – ShadowRanger

    1

    我會在你的迭代

    using reference = 
        typename std::conditional<Const, const node<T>&, node<T>&>::type; 
    

    所以,你的迭代器有隻被提領一次使用的參考,而不是指針。 (我會保持成員的指針雖然)。

    (公共)界面中的指針引入了對所有權的疑問。

    的使用語法是:

    for (auto& elem : *d) { 
        elem.val = elem.val - 1; 
    } 
    

    while (it != l->end()) { 
        const auto& elem = *it; 
        std::cout << elem.val << std::endl; 
        ++it; 
    } 
    

    你的迭代器確實在其相應的節點釋放這是常見的情況是無效的。