std :: giá trị mặc định của bản đồ


87

Có cách nào để xác định giá trị mặc định std::mapcủa operator[]lợi nhuận khi một chìa khóa không tồn tại?

Câu trả lời:


57

Không, không có. Giải pháp đơn giản nhất là viết hàm mẫu miễn phí của riêng bạn để làm điều này. Cái gì đó như:

#include <string>
#include <map>
using namespace std;

template <typename K, typename V>
V GetWithDef(const  std::map <K,V> & m, const K & key, const V & defval ) {
   typename std::map<K,V>::const_iterator it = m.find( key );
   if ( it == m.end() ) {
      return defval;
   }
   else {
      return it->second;
   }
}

int main() {
   map <string,int> x;
   ...
   int i = GetWithDef( x, string("foo"), 42 );
}

C ++ 11 Cập nhật

Mục đích: Giải thích cho các vùng chứa kết hợp chung, cũng như các tham số trình phân bổ và so sánh tùy chọn.

template <template<class,class,class...> class C, typename K, typename V, typename... Args>
V GetWithDef(const C<K,V,Args...>& m, K const& key, const V & defval)
{
    typename C<K,V,Args...>::const_iterator it = m.find( key );
    if (it == m.end())
        return defval;
    return it->second;
}

1
Giải pháp tốt. Bạn có thể muốn thêm một vài đối số mẫu để mẫu hàm hoạt động với các bản đồ không sử dụng các tham số mẫu mặc định cho trình so sánh và cấp phát.
sbi

3
+1, nhưng để cung cấp hành vi giống hệt như operator[]với giá trị mặc định, giá trị mặc định phải được chèn vào bản đồ bên trong if ( it == m.end() )khối
David Rodríguez - dribeas

12
@David Tôi giả sử OP không thực sự muốn hành vi đó. Tôi sử dụng một lược đồ tương tự để đọc cấu hình, nhưng tôi không muốn cấu hình được cập nhật nếu thiếu khóa.

2
Các tham số bool của @GMan được một số người cho là kiểu xấu vì bạn không thể biết bằng cách nhìn vào lệnh gọi (trái ngược với khai báo) chúng làm gì - trong trường hợp này, "true" có nghĩa là "sử dụng mặc định" hoặc "don ' t sử dụng mặc định "(hoặc cái gì khác hoàn toàn)? Một enum luôn rõ ràng hơn nhưng tất nhiên là nhiều mã hơn. Tôi đang nghĩ về chủ đề này, bản thân tôi.

2
Câu trả lời này không hoạt động nếu giá trị mặc định là nullptr, nhưng stackoverflow.com/a/26958878/297451 thì có.
Jon

44

Trong khi điều này không trả lời chính xác câu hỏi, tôi đã giải quyết vấn đề với mã như thế này:

struct IntDefaultedToMinusOne
{
    int i = -1;
};

std::map<std::string, IntDefaultedToMinusOne > mymap;

1
Đây là giải pháp tốt nhất cho tôi. Dễ thực hiện, rất linh hoạt và chung chung.
acegs

11

Tiêu chuẩn C ++ (23.3.1.2) chỉ định rằng giá trị mới được chèn vào được xây dựng mặc định, vì vậy mapbản thân nó không cung cấp cách thực hiện. Lựa chọn của bạn là:

  • Cung cấp cho kiểu giá trị một hàm tạo mặc định khởi tạo nó thành giá trị bạn muốn, hoặc
  • Gói bản đồ trong lớp của riêng bạn cung cấp giá trị mặc định và triển khai operator[]để chèn giá trị mặc định đó.

8
Chà, nói một cách chính xác thì giá trị mới được chèn vào là giá trị được khởi tạo (8.5.5) vì vậy: - nếu T là một loại lớp có hàm tạo do người dùng khai báo (12.1), thì hàm tạo mặc định cho T được gọi (và quá trình khởi tạo không đúng -định dạng nếu T không có hàm tạo mặc định có thể truy cập); - nếu T là kiểu lớp không liên hợp không có hàm tạo do người dùng khai báo, thì mọi thành phần dữ liệu không tĩnh và thành phần cơ sở của T đều được khởi tạo giá trị; - nếu T là một kiểu mảng, thì mỗi phần tử được khởi tạo giá trị; - nếu không, đối tượng được khởi tạo bằng 0
Tadeusz Kopec

6

Phiên bản chung hơn, hỗ trợ C ++ 98/3 và nhiều vùng chứa hơn

Hoạt động với các vùng chứa kết hợp chung, tham số mẫu duy nhất là chính loại vùng chứa.

Container hỗ trợ: std::map, std::multimap, std::unordered_map, std::unordered_multimap, wxHashMap, QMap, QMultiMap, QHash, QMultiHash,, vv

template<typename MAP>
const typename MAP::mapped_type& get_with_default(const MAP& m, 
                                             const typename MAP::key_type& key, 
                                             const typename MAP::mapped_type& defval)
{
    typename MAP::const_iterator it = m.find(key);
    if (it == m.end())
        return defval;

    return it->second;
}

Sử dụng:

std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");

Đây là một thực hiện tương tự bằng cách sử dụng một lớp wrapper, mà là tương tự như phương pháp get()của dictloại bằng Python: https://github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hpp

template<typename MAP>
struct map_wrapper
{
    typedef typename MAP::key_type K;
    typedef typename MAP::mapped_type V;
    typedef typename MAP::const_iterator CIT;

    map_wrapper(const MAP& m) :m_map(m) {}

    const V& get(const K& key, const V& default_val) const
    {
        CIT it = m_map.find(key);
        if (it == m_map.end())
            return default_val;

        return it->second;
    }
private:
    const MAP& m_map;
};

template<typename MAP>
map_wrapper<MAP> wrap_map(const MAP& m)
{
    return map_wrapper<MAP>(m);
}

Sử dụng:

std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");

MAP :: mapped_type & không an toàn để trả lại vì tên loại MAP :: mapped_type & defval có thể nằm ngoài phạm vi.
Joe C


5

Không có cách nào để chỉ định giá trị mặc định - nó luôn là giá trị được xây dựng theo mặc định (hàm tạo tham số không).

Trong thực tế, operator[]có thể làm nhiều hơn bạn mong đợi như thể một giá trị không tồn tại cho khóa đã cho trong bản đồ, nó sẽ chèn một giá trị mới với giá trị từ hàm tạo mặc định.


2
Đúng vậy, để tránh thêm các mục nhập mới, bạn có thể sử dụng findnó trả về trình lặp kết thúc nếu không có phần tử nào tồn tại cho một khóa nhất định.
Thomas Schaub

@ThomasSchaub Độ phức tạp về thời gian findtrong trường hợp đó là bao nhiêu?
ajaysinghnegi

5
template<typename T, T X>
struct Default {
    Default () : val(T(X)) {}
    Default (T const & val) : val(val) {}
    operator T & () { return val; }
    operator T const & () const { return val; }
    T val;
};

<...>

std::map<KeyType, Default<ValueType, DefaultValue> > mapping;

3
Sau đó sửa đổi nó để nó hoạt động. Tôi sẽ không bận tâm đến việc sửa chữa một trường hợp mà mã này không được thiết kế để thực hiện.
Thomas Eding

3

Giá trị được khởi tạo bằng cách sử dụng hàm tạo mặc định, như các câu trả lời khác nói. Tuy nhiên, sẽ hữu ích khi nói thêm rằng trong trường hợp các kiểu đơn giản (kiểu tích phân như kiểu int, float, con trỏ hoặc POD (kế hoạch dữ liệu cũ)), các giá trị được khởi tạo bằng 0 (hoặc bằng 0 bằng khởi tạo giá trị (cách này có hiệu quả điều tương tự), tùy thuộc vào phiên bản C ++ nào được sử dụng).

Dù sao, điểm mấu chốt là, các bản đồ với các loại đơn giản sẽ không tự động khởi tạo các mục mới. Vì vậy, trong một số trường hợp, không cần phải lo lắng về việc chỉ định rõ ràng giá trị ban đầu mặc định.

std::map<int, char*> map;
typedef char *P;
char *p = map[123],
    *p1 = P(); // map uses the same construct inside, causes zero-initialization
assert(!p && !p1); // both will be 0

Xem Các dấu ngoặc đơn sau tên loại có tạo ra sự khác biệt với mới không? để biết thêm chi tiết về vấn đề này.


2

Một cách giải quyết là sử dụng map::at()thay vì []. Nếu khóa không tồn tại, hãy atném một ngoại lệ. Thậm chí đẹp hơn, điều này cũng hoạt động đối với vectơ và do đó phù hợp với lập trình chung, nơi bạn có thể hoán đổi bản đồ với một vectơ.

Việc sử dụng giá trị tùy chỉnh cho khóa chưa đăng ký có thể nguy hiểm vì giá trị tùy chỉnh đó (như -1) có thể được xử lý sâu hơn trong mã. Với các trường hợp ngoại lệ, việc phát hiện lỗi sẽ dễ dàng hơn.


1

Có thể bạn có thể cung cấp một trình phân bổ tùy chỉnh, người phân bổ với giá trị mặc định mà bạn muốn.

template < class Key, class T, class Compare = less<Key>,
       class Allocator = allocator<pair<const Key,T> > > class map;

4
operator[]trả về một đối tượng được tạo bằng cách gọi T(), bất kể trình cấp phát làm gì.
sbi

1
@sbi: Bản đồ không gọi constructphương thức phân bổ sao? Tôi nghĩ có thể thay đổi điều đó. Tuy nhiên, tôi nghi ngờ một constructchức năng không phải new(p) T(t);là một cái gì đó không được hình thành tốt. EDIT: Trong nhận thức muộn màng đó là điều ngu ngốc, nếu không tất cả các giá trị sẽ giống nhau: P Cà phê của tôi đâu ...
GManNickG

1
@GMan: bản sao C ++ 03 của tôi cho biết (trong 23.3.1.2) operator[]trả về (*((insert(make_pair(x, T()))).first)).second. Vì vậy, trừ khi tôi thiếu một cái gì đó, câu trả lời này là sai.
sbi

Bạn đúng rồi. Nhưng điều đó có vẻ sai đối với tôi. Tại sao họ không sử dụng chức năng cấp phát để chèn?
VDVLeon

2
@sbi: Không, tôi đồng ý câu trả lời này là sai, nhưng vì một lý do khác. Trình biên dịch thực sự làm insertvới a T(), nhưng chèn bên trong là khi nó sẽ sử dụng bộ cấp phát lấy bộ nhớ cho một lệnh mới Tsau đó gọi constructbộ nhớ đó với tham số mà nó đã được cung cấp, đó là T(). Vì vậy, thực sự có thể thay đổi hành vi của operator[]nó để trả về một cái gì đó khác, nhưng bộ cấp phát không thể phân biệt tại sao nó được gọi. Vì vậy, ngay cả khi chúng tôi đã constructbỏ qua tham số của nó và sử dụng giá trị đặc biệt của chúng tôi, điều đó có nghĩa là mọi phần tử được xây dựng đều có giá trị đó, điều này thật tệ.
GManNickG

1

Mở rộng trên câu trả lời https://stackoverflow.com/a/2333816/272642 , hàm mẫu này sử dụng std::map's key_typemapped_typetypedefs để suy ra loại keydef. Điều này không hoạt động với các vùng chứa không có các typedef này.

template <typename C>
typename C::mapped_type getWithDefault(const C& m, const typename C::key_type& key, const typename C::mapped_type& def) {
    typename C::const_iterator it = m.find(key);
    if (it == m.end())
        return def;
    return it->second;
}

Điều này cho phép bạn sử dụng

std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);

mà không cần đúc các đối số như std::string("a"), (int*) NULL.


1

Sử dụng std::map::insert().

Nhận ra rằng tôi đến với bữa tiệc này khá muộn, nhưng nếu bạn quan tâm đến hành vi của operator[]với mặc định tùy chỉnh (nghĩa là, hãy tìm phần tử bằng khóa đã cho, nếu nó không có mặt, hãy chèn một phần tử vào bản đồ với đã chọn giá trị mặc định và trả về một tham chiếu đến giá trị mới được chèn vào hoặc giá trị hiện có), Đã có một hàm khả dụng cho bạn trước C ++ 17 : std::map::insert(). insertsẽ không thực sự chèn nếu khóa đã tồn tại, nhưng thay vào đó trả về một trình lặp về giá trị hiện có.

Giả sử, bạn muốn có một bản đồ chuỗi-thành-int và chèn giá trị mặc định là 42 nếu khóa chưa xuất hiện:

std::map<std::string, int> answers;

int count_answers( const std::string &question)
{
    auto  &value = answers.insert( {question, 42}).first->second;
    return value++;
}

int main() {

    std::cout << count_answers( "Life, the universe and everything") << '\n';
    std::cout << count_answers( "Life, the universe and everything") << '\n';
    std::cout << count_answers( "Life, the universe and everything") << '\n';
    return 0;
}

mà sẽ xuất ra 42, 43 và 44.

Nếu chi phí xây dựng giá trị bản đồ cao (nếu sao chép / di chuyển khóa hoặc loại giá trị là đắt), điều này dẫn đến một hình phạt hiệu suất đáng kể, mà tôi nghĩ sẽ bị phá vỡ với C ++ 17's try_emplace.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.