2010-02-07 18 views
58

Tôi tìm thấy thao tác cập nhật trên thiết lập tẻ nhạt vì không có API như vậy trên cppreference. Vì vậy, những gì tôi hiện đang làm là sth như thế này:Cập nhật thiết lập C++ STL là tẻ nhạt: Tôi không thể thay đổi một phần tử tại vị trí

//find element in set by iterator 
Element copy = *iterator; 
... // update member value on copy, varies 
Set.erase(iterator); 
Set.insert(copy); 

Về cơ bản, trình trả về vòng lặp của Bộ là một const_iterator và bạn không thể thay đổi giá trị trực tiếp.

Có cách nào tốt hơn để thực hiện việc này không? Hoặc có lẽ tôi nên ghi đè thiết lập bằng cách tạo của riêng tôi (mà tôi không biết chính xác cách thức hoạt động ..)

+3

Tạo một hàm nội dòng nếu bạn thấy bằng cách sử dụng 2 câu lệnh đã quá tẻ nhạt. – kennytm

+0

KennyTM đánh vào đầu. Không có nhược điểm hiệu quả để làm điều này, do đó, chỉ cần làm điều đó rồi! :-P –

+1

Nếu bạn viết một hàm cập nhật, bạn có thể muốn mô hình hóa nó theo cùng cách như Boost.MultiIndex: http://www.boost.org/doc/libs/release/libs/multi_index/doc/tutorial/basics .html # ord_updating –

Trả lời

64

set lợi nhuận const_iterators (tiêu chuẩn nói set<T>::iteratorconst, và rằng set<T>::const_iteratorset<T>::iterator có thể trên thực tế là như nhau loại - xem 23.2.4/6 trong n3000.pdf) bởi vì nó là một container theo thứ tự. Nếu nó trả về số iterator thông thường, bạn sẽ được phép thay đổi giá trị của các mục từ bên dưới vùng chứa, có khả năng thay đổi thứ tự.

Giải pháp của bạn là cách thành ngữ để thay đổi các mục trong một số set.

+0

Thành viên của (không const) 'std :: set' không trả về' const_iterator' và bạn * có thể * sửa đổi các phần tử của nó nếu bạn cẩn thận. Tại sao câu trả lời này (không chính xác) nhận được rất nhiều upvotes? Tôi đang thiếu gì? – avakar

+1

Câu trả lời của tôi là đúng - bạn đã sai. Tôi đã cập nhật bài đăng của mình với tham chiếu đến tiêu chuẩn. –

+6

Terry, cảm ơn bạn đã thảo luận. Tôi đã kiểm tra lại: báo cáo lỗi thực sự đã được gửi vào năm 1998, nhưng không được đưa vào C++ 03. Nó sẽ được vào C++ 0x. Vì vậy, trong khi câu trả lời của bạn là không chính xác như xa như thư hiện tại của tiêu chuẩn là có liên quan, nó là chính xác như xa như ý định đi. +1. – avakar

6

Bạn có thể muốn sử dụng sơ đồ :: std. Sử dụng phần tử Element ảnh hưởng đến thứ tự khóa và đặt tất cả Element làm giá trị. Sẽ có một số bản sao dữ liệu nhỏ, nhưng bạn sẽ có các bản cập nhật dễ dàng hơn (và có thể nhanh hơn).

8

Cập nhật: Mặc dù sau đây là đúng như hiện nay, hành vi được coi là defect và sẽ được thay đổi trong phiên bản chuẩn sắp tới. Làm thế nào rất buồn.


Có một số điểm khiến câu hỏi của bạn khá khó hiểu.

  1. Chức năng có thể trả lại giá trị, các lớp không thể. std::set là một lớp học, và do đó không thể trả lại bất cứ điều gì.
  2. Nếu bạn có thể gọi s.erase(iter), thì iter không phải là const_iterator. erase yêu cầu trình lặp không phải là const.
  3. Tất cả các chức năng thành viên của std::set trả về một trình lặp trở lại trình lặp không phải là const miễn là tập hợp không phải là const.

Bạn được phép thay đổi giá trị của thành phần của tập hợp miễn là bản cập nhật không thay đổi thứ tự của các phần tử. Mã sau đây biên dịch và hoạt động tốt.

#include <set> 

int main() 
{ 
    std::set<int> s; 
    s.insert(10); 
    s.insert(20); 

    std::set<int>::iterator iter = s.find(20); 

    // OK 
    *iter = 30; 

    // error, the following changes the order of elements 
    // *iter = 0; 
} 

Nếu cập nhật của bạn thay đổi thứ tự các phần tử, thì bạn phải xóa và lắp lại.

+0

Mã đó không biên dịch, ít nhất là trong VC10 - vì những lý do tôi nêu trong bài viết của tôi. Điều này có thể đã thay đổi gần đây (là một "sửa lỗi" cho tiêu chuẩn), tôi nghĩ rằng đó là trong C + + 03, nhưng tôi có thể sai. –

+0

Vâng, tôi đang xem 23.1.2 [lib.associative.reqmts] của C++ 03, bảng 69 và nó nói: "a.find (k): iterator; const_iterator cho hằng số a". – avakar

+0

Và tôi vừa kiểm tra bản nháp cho C++ 0x và không có thay đổi (điều này không ngạc nhiên và nó sẽ phá vỡ nhiều mã). – avakar

21

Có 2 cách để làm điều này, trong trường hợp đơn giản:

  • Bạn có thể sử dụng mutable trên biến mà không phải là một phần của khóa
  • Bạn có thể chia lớp học của bạn trong một cặp KeyValue (và sử dụng một số std::map)

Bây giờ, câu hỏi đặt ra cho trường hợp khó hiểu: điều gì xảy ra khi cập nhật thực sự sửa đổi key một phần của đối tượng? Cách tiếp cận của bạn hoạt động, mặc dù tôi thừa nhận nó tẻ nhạt.

+3

+1 có thể tắt tiếng trên thành viên không phải là thành viên quan trọng là một ý tưởng tuyệt vời – kfmfe04

+2

thêm có thể thay đổi trên thành viên không chính có thể là ok nếu một số lớp nội bộ/riêng tư của nó, nhưng nó vẫn là một bản hack bẩn! Ngay sau khi lớp học được tiếp xúc với một số người dùng, id không bao giờ dám sử dụng thay đổi cho các thành viên không có nghĩa là có thể thay đổi được! Đó là điều ác! –

1

Nếu bộ của bạn chứa các đối tượng bạn muốn thay đổi, hãy đảm bảo bạn lưu trữ con trỏ của chúng trong bộ này.

Điều này không ngăn bạn chèn nhiều đối tượng có cùng giá trị (bạn không thể chèn cùng một đối tượng nhiều lần) nhưng hữu ích nếu bạn muốn một vùng chứa chèn nhanh và xóa.

0

Đây là nhanh hơn trong một số trường hợp:

std::pair<std::set<int>::iterator, bool> result = Set.insert(value); 
if (!result.second) { 
    Set.erase(result.first); 
    Set.insert(value); 
} 

Nếu giá trị thường không phải là đã có trong std :: set sau đó điều này có thể có hiệu suất tốt hơn.

2

Tôi gặp phải vấn đề tương tự trong C++ 11, trong đó thực sự ::std::set<T>::iterator là không đổi và do đó không cho phép thay đổi nội dung của nó, ngay cả khi chúng ta biết chuyển đổi sẽ không ảnh hưởng đến bất biến <. Bạn có thể khắc phục điều này bằng cách gói ::std::set vào một loại mutable_set hoặc viết một wrapper cho các nội dung:

template <typename T> 
    struct MutableWrapper { 
    mutable T data; 
    MutableWrapper(T const& data) : data(data) {} 
    MutableWrapper(T&& data) : data(data) {} 
    MutableWrapper const& operator=(T const& data) { this->data = data; } 
    operator T&() const { return data; } 
    T* operator->() const { return &data; } 
    friend bool operator<(MutableWrapper const& a, MutableWrapper const& b) { 
     return a.data < b.data; 
    } 
    friend bool operator==(MutableWrapper const& a, MutableWrapper const& b) { 
     return a.data == b.data; 
    } 
    friend bool operator!=(MutableWrapper const& a, MutableWrapper const& b) { 
     return a.data != b.data; 
    } 
    }; 

Tôi tìm thấy điều này đơn giản hơn nhiều và nó hoạt động trong 90% các trường hợp mà không cần người dùng thậm chí nhận thấy có được một cái gì đó giữa bộ và loại thực tế.

+0

Ý tưởng thú vị, tôi sẽ cố nhớ điều này. –

+1

@MarkRansom: Có, nhưng chỉ để chắc chắn hơn, cần lưu ý rằng điều này chỉ có thể được sử dụng nếu sửa đổi dữ liệu được lưu trữ phía sau trình lặp được ** đảm bảo ** không thay đổi thứ tự của bộ này. Nếu không, đây là UB và nó * sẽ * phá vỡ! (Chỉ cần nhắc lại điều này để chắc chắn không ai bắn mình vào chân. Tôi không ngụ ý bạn, đặc biệt, không nhận ra điều này.) – bitmask

+0

+1 điều này là lý tưởng nếu bạn muốn các mục được sắp xếp độc đáo dựa trên một số phím (mà đặt là vùng chứa thích hợp) nhưng cũng muốn giữ một số 'siêu dữ liệu' với chúng có thể thay đổi – stijn

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