มีวิธีระบุผลตอบแทนstd::map
ของค่าเริ่มต้นoperator[]
เมื่อไม่มีคีย์หรือไม่?
มีวิธีระบุผลตอบแทนstd::map
ของค่าเริ่มต้นoperator[]
เมื่อไม่มีคีย์หรือไม่?
คำตอบ:
ไม่มีไม่มี วิธีแก้ไขที่ง่ายที่สุดคือเขียนฟังก์ชันเทมเพลตฟรีของคุณเองเพื่อทำสิ่งนี้ สิ่งที่ต้องการ:
#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
วัตถุประสงค์: บัญชีสำหรับคอนเทนเนอร์ทั่วไปที่เชื่อมโยงตลอดจนพารามิเตอร์ตัวเปรียบเทียบและตัวจัดสรรที่เป็นทางเลือก
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[]
ค่าเริ่มต้นควรใส่ค่าเริ่มต้นลงในแผนที่ภายในif ( it == m.end() )
บล็อก
แม้ว่าสิ่งนี้จะไม่ตอบคำถามอย่างตรงไปตรงมา แต่ฉันได้หลีกเลี่ยงปัญหาด้วยรหัสเช่นนี้:
struct IntDefaultedToMinusOne
{
int i = -1;
};
std::map<std::string, IntDefaultedToMinusOne > mymap;
มาตรฐาน C ++ (23.3.1.2) ระบุว่าค่าที่แทรกใหม่เป็นค่าดีฟอลต์ที่สร้างขึ้นmap
เองดังนั้นจึงไม่มีวิธีดำเนินการ ทางเลือกของคุณคือ:
operator[]
เพื่อแทรกค่าเริ่มต้นนั้นC ++ 17 ให้try_emplace
สิ่งที่ตรงนี้ ใช้คีย์และรายการอาร์กิวเมนต์สำหรับตัวสร้างค่าและส่งคืนคู่: an iterator
และ a bool
: http://en.cppreference.com/w/cpp/container/map/try_emplace
เวอร์ชันทั่วไปเพิ่มเติมรองรับ C ++ 98/03 และคอนเทนเนอร์อื่น ๆ
ทำงานร่วมกับคอนเทนเนอร์ทั่วไปที่เชื่อมโยงพารามิเตอร์เทมเพลตเดียวคือประเภทคอนเทนเนอร์เอง
ภาชนะที่รองรับ: std::map
, std::multimap
, std::unordered_map
, std::unordered_multimap
, wxHashMap
, QMap
, QMultiMap
, QHash
, QMultiHash
ฯลฯ
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;
}
การใช้งาน:
std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");
นี่คือการดำเนินการที่คล้ายกันโดยใช้คลาสกระดาษห่อซึ่งเป็นขึ้นคล้ายกับวิธีการget()
ของdict
ชนิดในหลาม: 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);
}
การใช้งาน:
std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");
ไม่มีวิธีใดในการระบุค่าเริ่มต้น - เป็นค่าที่สร้างโดยค่าเริ่มต้นเสมอ (ตัวสร้างพารามิเตอร์เป็นศูนย์)
ในความเป็นจริงoperator[]
อาจทำได้มากกว่าที่คุณคาดหวังราวกับว่าไม่มีค่าสำหรับคีย์ที่กำหนดในแผนที่มันจะแทรกค่าใหม่ด้วยค่าจากตัวสร้างเริ่มต้น
find
ซึ่งจะคืนค่าตัววนซ้ำท้ายถ้าไม่มีองค์ประกอบสำหรับคีย์ที่กำหนด
find
ในกรณีนั้น?
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;
ค่าเริ่มต้นโดยใช้ตัวสร้างเริ่มต้นตามที่คำตอบอื่น ๆ กล่าว อย่างไรก็ตามมีประโยชน์ในการเพิ่มว่าในกรณีของประเภทที่เรียบง่าย (ประเภทอินทิกรัลเช่นประเภท int, float, pointer หรือ POD (วางแผนข้อมูลเก่า)) ค่าจะเริ่มต้นเป็นศูนย์ (หรือเป็นศูนย์โดยค่าเริ่มต้น (ซึ่งมีประสิทธิภาพ สิ่งเดียวกัน) ขึ้นอยู่กับเวอร์ชันของ C ++ ที่ใช้)
อย่างไรก็ตามบรรทัดล่างคือแผนที่ที่มีประเภทง่าย ๆ จะเริ่มต้นรายการใหม่โดยอัตโนมัติ ดังนั้นในบางกรณีไม่จำเป็นต้องกังวลเกี่ยวกับการระบุค่าเริ่มต้นที่เป็นค่าเริ่มต้นอย่างชัดเจน
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
ดูวงเล็บหลังชื่อประเภทสร้างความแตกต่างกับ new หรือไม่ สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับเรื่องนี้
วิธีแก้ปัญหาอย่างหนึ่งคือการใช้map::at()
แทน[]
. หากไม่มีคีย์at
ให้แสดงข้อยกเว้น แม้จะดีกว่านี้ก็ยังใช้ได้กับเวกเตอร์และเหมาะสำหรับการเขียนโปรแกรมทั่วไปที่คุณสามารถสลับแผนที่กับเวกเตอร์ได้
การใช้ค่าที่กำหนดเองสำหรับคีย์ที่ไม่ได้ลงทะเบียนอาจเป็นอันตรายเนื่องจากค่าที่กำหนดเองนั้น (เช่น -1) อาจถูกประมวลผลเพิ่มเติมในโค้ด ด้วยข้อยกเว้นการตรวจพบจุดบกพร่องได้ง่ายขึ้น
บางทีคุณอาจให้ผู้จัดสรรแบบกำหนดเองซึ่งจัดสรรด้วยค่าเริ่มต้นที่คุณต้องการ
template < class Key, class T, class Compare = less<Key>,
class Allocator = allocator<pair<const Key,T> > > class map;
operator[]
ส่งคืนวัตถุที่สร้างโดยการเรียกใช้T()
ไม่ว่าผู้จัดสรรจะทำอะไรก็ตาม
construct
วิธีการจัดสรรหรือไม่? ฉันคิดว่ามันน่าจะเปลี่ยนได้ ฉันสงสัยว่าconstruct
ฟังก์ชันที่ทำอย่างอื่นที่new(p) T(t);
ไม่ใช่รูปแบบที่ดีแม้ว่า แก้ไข: ในการมองย้อนกลับไปที่โง่เขลามิฉะนั้นค่าทั้งหมดจะเหมือนกัน: P กาแฟของฉันอยู่ที่ไหน ...
operator[]
(*((insert(make_pair(x, T()))).first)).second
คำตอบนี้ไม่ถูกต้อง
insert
มีT()
แต่แทรกอยู่ภายในเมื่อมันจะใช้หน่วยความจำจัดสรรได้รับสำหรับใหม่T
โทรแล้วในหน่วยความจำที่มีพารามิเตอร์ที่ได้รับซึ่งเป็นconstruct
T()
ดังนั้นจึงเป็นไปได้ที่จะเปลี่ยนพฤติกรรมoperator[]
เพื่อให้มันส่งคืนอย่างอื่น แต่ผู้จัดสรรไม่สามารถแยกความแตกต่างได้ว่าทำไมจึงถูกเรียก ดังนั้นแม้ว่าเราจะconstruct
เพิกเฉยต่อพารามิเตอร์และใช้ค่าพิเศษของเรานั่นก็หมายความว่าทุกองค์ประกอบที่สร้างขึ้นมีค่านั้นซึ่งไม่ดี
ขยายตัวในคำตอบhttps://stackoverflow.com/a/2333816/272642 , แม่แบบนี้ฟังก์ชั่นการใช้งานstd::map
s' key_type
และmapped_type
typedefs จะอนุมานชนิดของและkey
def
สิ่งนี้ใช้ไม่ได้กับคอนเทนเนอร์ที่ไม่มี typedef เหล่านี้
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;
}
สิ่งนี้ช่วยให้คุณสามารถใช้
std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);
std::string("a"), (int*) NULL
โดยไม่จำเป็นต้องโยนข้อโต้แย้งเช่น
ใช้std::map::insert()
.
ตระหนักว่าฉันไปงานปาร์ตี้นี้ค่อนข้างช้า แต่ถ้าคุณสนใจพฤติกรรมของoperator[]
ค่าเริ่มต้นที่กำหนดเอง (นั่นคือค้นหาองค์ประกอบด้วยคีย์ที่กำหนดหากไม่มีอยู่ให้แทรกองค์ประกอบในแผนที่ด้วย ได้รับเลือกค่าเริ่มต้นและผลตอบแทนอ้างอิงกับทั้งค่าแทรกใหม่หรือค่าที่มีอยู่) มีอยู่แล้วฟังก์ชั่นที่มีให้คุณก่อน C ++ std::map::insert()
17:insert
จะไม่แทรกหากมีคีย์อยู่แล้ว แต่จะส่งตัววนซ้ำกลับเป็นค่าที่มีอยู่แทน
สมมติว่าคุณต้องการแมปของ string-to-int และแทรกค่าเริ่มต้นเป็น 42 หากยังไม่มีคีย์:
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;
}
ซึ่งควรส่งออก 42, 43 และ 44
หากค่าใช้จ่ายในการสร้างมูลค่าแผนที่อยู่ในระดับสูง (ถ้าทั้งคัดลอก / ย้ายที่สำคัญหรือประเภทค่าที่มีราคาแพง) นี้มาลงโทษประสิทธิภาพอย่างมีนัยสำคัญซึ่งผมคิดว่าจะถูกโกงด้วย try_emplace
C