2013-07-25 38 views
17

Giả sử tôi có một bộ unique_ptr:Sử dụng một std :: unordered_set của std :: unique_ptr

std::unordered_set <std::unique_ptr <MyClass>> my_set; 

Tôi không chắc chắn những cách an toàn để kiểm tra xem một con trỏ được tồn tại trong các thiết lập là những gì. Cách thông thường để thực hiện nó có thể là gọi my_set.find(), nhưng tôi phải làm gì để tham số?

Tất cả những gì tôi có từ bên ngoài là con trỏ thô. Vì vậy, tôi phải tạo một unique_ptr từ con trỏ, vượt qua nó để find() và sau đó release() con trỏ đó, nếu không thì đối tượng sẽ bị hủy (hai lần). Tất nhiên, quá trình này có thể được thực hiện trong một hàm, vì vậy người gọi có thể truyền con trỏ thô và tôi thực hiện chuyển đổi.

Phương pháp này có an toàn không? Có cách nào tốt hơn để làm việc với một tập hợp các unique_ptr?

+0

Cảm ơn. Tôi không cần phải di chuyển hoặc sao chép bất cứ điều gì, vì vậy unique_ptr là okay. Tôi chỉ cần để cho người gọi cho tôi một con trỏ thô, và tôi cần phải kiểm tra nếu một unique_ptr phù hợp tồn tại trong bộ này. – cfa45ca55111016ee9269f0a52e771

+1

'unique_ptr' rõ ràng không phải là những gì bạn cần, vì bạn rõ ràng có các con trỏ khác cho đối tượng. –

+2

Chủ sở hữu của unique_ptr là chủ sở hữu duy nhất của bộ nhớ và tất cả những người khác chỉ giữ tham chiếu. Tôi có thể sử dụng chia sẻ :: ptr trong chủ sở hữu và weak_ptr ở khắp mọi nơi khác, nhưng sau đó mỗi đối tượng được tham chiếu bởi một shared_ptr duy nhất. Tôi không cần chia sẻ, chỉ cần một chủ sở hữu duy nhất – cfa45ca55111016ee9269f0a52e771

Trả lời

17

Bạn cũng có thể sử dụng một deleter tùy chọn không làm bất cứ điều gì.

template<class T> 
struct maybe_deleter{ 
    bool _delete; 
    explicit maybe_deleter(bool doit = true) : _delete(doit){} 

    void operator()(T* p) const{ 
    if(_delete) delete p; 
    } 
}; 

template<class T> 
using set_unique_ptr = std::unique_ptr<T, maybe_deleter<T>>; 

template<class T> 
set_unique_ptr<T> make_find_ptr(T* raw){ 
    return set_unique_ptr<T>(raw, maybe_deleter<T>(false)); 
} 

// ... 

int* raw = new int(42); 
std::unordered_set<set_unique_ptr<int>> myset; 
myset.insert(set_unique_ptr<int>(raw)); 

auto it = myset.find(make_find_ptr(raw)); 

Live example.

+0

+1 cho sự thanh lịch – sehe

+0

Tôi muốn bị xem xét để xem xét một hàm tạo [implicit' ở đây để bạn có thể 'set_unique_ptr (raw, false)'] (http://coliru.stacked-crooked.com/view?id=69a8470528f5380f02eba643929357e7-e54ee7a04e4b807da0930236d4cc94dc) thay thế. Suy nghĩ? (Tôi biết tôi biết. Chuyển đổi ngầm định, _evil_. Nhưng, cụ thể là, bất kỳ bắt nào đối với trường hợp sử dụng _this specific_ này?) – sehe

8

Bạn có thể sử dụng std::map<MyClass*, std::unique_ptr<MyClass>> thay vì đặt. Sau đó, bạn có thể thêm các yếu tố như thế này:

std::unique_ptr<MyClass> instance(new MyClass); 
map.emplace(instance.get(), std::move(instance)); 
+0

Ý tưởng hay! Nhưng nó làm cho bộ lớn gấp đôi. Nó không phải là một vấn đề trong trường hợp của tôi, nhưng chỉ cần tự hỏi - phương pháp tôi mô tả công việc và hoàn toàn an toàn? (Nhân tiện, nó phải là unordered_map, không phải bản đồ) – cfa45ca55111016ee9269f0a52e771

+3

-1. Có gì sai với 'std :: set'? Trong trường hợp này, 'std :: map' là xấu. Khóa và giá trị là cùng một đối tượng cơ bản! – Nawaz

+1

@Nawaz unique_ptr giữ bộ nhớ trong khi con trỏ thô được sử dụng để tìm() và tất cả các phương thức khác tìm kiếm theo khóa. – cfa45ca55111016ee9269f0a52e771

10

Lưu ý rằng khả năng làm tra cứu heterogenous trên container tiêu chuẩn là chủ đề của một số đề xuất.

http://cplusplus.github.io/LWG/lwg-proposal-status.html danh sách

  • N3465 Thêm tra cứu so sánh không đồng nhất để container kết hợp cho TR2 (Rev 2) [Xử lý với N3573]
  • N2882 id.
  • N3573 mở rộng heterogenous để container có thứ tự [Xử lý với N3465]

Đặc biệt là vẻ bề ngoài sau như nó sẽ bao gồm trường hợp sử dụng của bạn.

Cho đến nay, đây là một cách giải quyết thay thế IMO không phải là rất đẹp, nhưng làm việc (O (n)):

#include <iterator> 
#include <iostream> 
#include <algorithm> 

#include <unordered_set> 
#include <memory> 

#include <cassert> 

struct MyClass {}; 

template <typename T> 
struct RawEqualTo 
{ 
    RawEqualTo(T const* raw) : raw(raw) {} 

    bool operator()(T const* p) const 
     { return raw == p; } 
    bool operator()(std::unique_ptr<T> const& up) const 
     { return raw == up.get(); } 

    private: 
    T const* raw; 
}; 


using namespace std; 
int main() 
{ 
    std::unordered_set <std::unique_ptr <MyClass>> my_set; 

    my_set.insert(std::unique_ptr<MyClass>(new MyClass)); 
    my_set.insert(std::unique_ptr<MyClass>(new MyClass)); 

    auto raw = my_set.begin()->get(); 

    bool found = end(my_set) != std::find_if(begin(my_set), end(my_set), RawEqualTo<MyClass>(raw)); 
    assert(found); 

    raw = new MyClass; 

    found = end(my_set) != std::find_if(begin(my_set), end(my_set), RawEqualTo<MyClass>(raw)); 
    assert(!found); 

    delete raw; 
} 

Warning Nó cũng rất hiệu quả, dĩ nhiên rồi.

+0

+1 Để đề cập đến tra cứu không đồng nhất (và ít nhất là cảnh báo 'std :: find_if 'thúc đẩy việc sử dụng' std :: unordered_set' ad absurdum). –

+0

Các phần mở rộng dị thường giải quyết được vấn đề, chúng thực hiện * chính xác * những gì tôi cần :-) Nhưng giải pháp bạn đề xuất ở đây thực sự là không hiệu quả. Tôi cần O (1) trung bình của unordered_set :: tìm – cfa45ca55111016ee9269f0a52e771

+0

@Nawaz Bởi vì một 'std :: shared_ptr' sẽ hoàn toàn thất lạc nếu bộ nhớ không được chia sẻ. –

4

Nếu mục tiêu là thời gian không đổi để tra cứu, tôi không nghĩ rằng có một giải pháp. std::unordered_set<std::unique_ptr<MyClass>>::find yêu cầu số std::unique_ptr<MyClass> làm đối số. Bạn sẽ phải thay đổi vùng chứa hoặc thay đổi loại được chứa.

Một khả năng có thể để thay thế std::unique_ptr với std::shared_ptr, và thay đổi còn lại của mã sao cho tất cả MyClass được đưa vào một shared_ptr ngay sau khi họ được tạo ra, và chỉ được thao tác thông qua con trỏ chia sẻ. Về mặt logic, điều này có lẽ mạch lạc hơn: unique_ptr khá nhiều ngụ ý (theo tên của nó, cũng như ngữ nghĩa của nó) rằng có không phải là con trỏ khác cho đối tượng. Mặt khác, bạn có thể không thể sử dụng shared_ptr, nếu ví dụ: MyClass có con trỏ đến khác MyClass, có thể tạo chu trình.

Ngược lại, nếu bạn có thể chấp nhận truy cập O (lg n), chứ không phải là truy cập thường xuyên (sự khác biệt thường không trở thành đáng chú ý cho đến khi các bảng là khá lớn), bạn có thể sử dụng một std::vector<MyClass>, sử dụng std::lower_bound để giữ nó được sắp xếp. Không giống như std::unordered_set<>::find, std::lower_bound không không yêu cầu giá trị đích phải có cùng loại với số value_type của chuỗi; tất cả các bạn phải làm là để đảm bảo rằng họ có thể so sánh, nói bằng cách cung cấp một đối tượng Compare dọc theo dòng:

class MyClassPtrCompare 
{ 
    std::less<MyClass const*> cmp; 
public: 
    bool operator()(std::unique_ptr<MyClass> const& lhs, 
        std::unique_ptr<MyClass> const& rhs) const 
    { 
     return cmp(lhs.get(), rhs.get()); 
    } 
    bool operator()(MyClass const* lhs, 
        std::unique_ptr<MyClass> const& rhs) const 
    { 
     return cmp(lhs, rhs.get()); 
    } 
    bool operator()(std::unique_ptr<MyClass> const& lhs, 
        MyClass const* rhs) const 
    { 
     return cmp(lhs.get(), rhs); 
    } 
    bool operator()(MyClass const* lhs, 
        MyClass const* rhs) const 
    { 
     return cmp(lhs, rhs); 
    } 
}; 

Insertion có thể liên quan đến một số lần di chuyển, nhưng di chuyển một std::unique_ptr nên khá rẻ và địa điểm được cải thiện của giải pháp này có thể bù đắp thời gian chạy bổ sung chi phí mà nó áp đặt theo cách khác.

+0

+1 Có vẻ như một sự thỏa hiệp tốt. Lạ lùng không ai trong số những người ủng hộ 'find_if' đã trang bị điều này cả (nhưng Ok, cũng không nghĩ về điều này). –

+0

Ý tưởng hay. Hiện tại tôi không nhớ O (lg n) nhưng tôi dự định hỗ trợ số lượng lớn các đối tượng và nhiều truy vấn, vì vậy tôi thích sử dụng phương pháp O (1) trung bình nếu có thể – cfa45ca55111016ee9269f0a52e771

+2

@ fr33domlover Tuy nhiên, bạn có thể ngạc nhiên về điều đó sắp xếp vector sẽ hành xử trong thực tế, tôi đoán. –

Các vấn đề liên quan