Có cách nào để xác định giá trị mặc định std::map
của operator[]
lợi nhuận khi một chìa khóa không tồn tại?
Câu trả lời:
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;
}
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
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;
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 map
bản thân nó không cung cấp cách thực hiện. Lựa chọn của bạn là:
operator[]
để chèn giá trị mặc định đó.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 dict
loạ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");
C ++ 17 cung cấp try_emplace
chính xác điều này. Nó nhận một khóa và một danh sách đối số cho hàm tạo giá trị và trả về một cặp: an iterator
và a bool
.: Http://en.cppreference.com/w/cpp/container/map/try_emplace
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.
find
nó 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.
find
trong trường hợp đó là bao nhiêu?
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;
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.
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 at
né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.
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;
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ì.
construct
phươ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 construct
chứ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 ...
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.
insert
vớ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 T
sau đó gọi construct
bộ 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 đã construct
bỏ 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ệ.
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_type
và mapped_type
typedefs để suy ra loại key
và def
. Đ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
.
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()
. insert
sẽ 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
.