std :: ค่าเริ่มต้นแผนที่


คำตอบ:


58

ไม่มีไม่มี วิธีแก้ไขที่ง่ายที่สุดคือเขียนฟังก์ชันเทมเพลตฟรีของคุณเองเพื่อทำสิ่งนี้ สิ่งที่ต้องการ:

#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;
}

1
ทางออกที่ดี คุณอาจต้องการเพิ่มอาร์กิวเมนต์ของเทมเพลตเพื่อให้เทมเพลตฟังก์ชันทำงานกับแผนที่ที่ไม่ใช้พารามิเตอร์เทมเพลตเริ่มต้นสำหรับตัวเปรียบเทียบและตัวจัดสรร
sbi

3
+1 แต่เพื่อให้มีลักษณะการทำงานเหมือนกับoperator[]ค่าเริ่มต้นควรใส่ค่าเริ่มต้นลงในแผนที่ภายในif ( it == m.end() )บล็อก
David Rodríguez - dribeas

13
@ เดวิดฉันสมมติว่า OP ไม่ต้องการพฤติกรรมนั้นจริงๆ ฉันใช้รูปแบบที่คล้ายกันสำหรับการอ่านการกำหนดค่า แต่ฉันไม่ต้องการให้การกำหนดค่าอัปเดตหากไม่มีคีย์

2
พารามิเตอร์บูลของ @GMan บางคนคิดว่าเป็นรูปแบบที่ไม่ดีเพราะคุณไม่สามารถบอกได้โดยดูจากการโทร (ตรงข้ามกับการประกาศ) ว่าพวกเขาทำอะไร - ในกรณีนี้ "จริง" หมายถึง "ใช้ค่าเริ่มต้น" หรือ "ไม่" ไม่ใช้ค่าเริ่มต้น "(หรืออย่างอื่นทั้งหมด)? enum นั้นชัดเจนกว่าเสมอ แต่แน่นอนว่ามีรหัสมากกว่า ฉันอยู่ในสองความคิดในเรื่องนี้ตัวเอง

2
คำตอบนี้ใช้ไม่ได้หากค่าเริ่มต้นเป็น nullptr แต่stackoverflow.com/a/26958878/297451ทำ
จอน

45

แม้ว่าสิ่งนี้จะไม่ตอบคำถามอย่างตรงไปตรงมา แต่ฉันได้หลีกเลี่ยงปัญหาด้วยรหัสเช่นนี้:

struct IntDefaultedToMinusOne
{
    int i = -1;
};

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

1
นี่คือทางออกที่ดีที่สุดสำหรับฉัน ใช้งานง่ายยืดหยุ่นมากและใช้งานได้ทั่วไป
acegs

11

มาตรฐาน C ++ (23.3.1.2) ระบุว่าค่าที่แทรกใหม่เป็นค่าดีฟอลต์ที่สร้างขึ้นmapเองดังนั้นจึงไม่มีวิธีดำเนินการ ทางเลือกของคุณคือ:

  • กำหนดให้ประเภทค่าเป็นตัวสร้างเริ่มต้นที่เริ่มต้นค่าที่คุณต้องการหรือ
  • รวมแผนที่ในคลาสของคุณเองที่ให้ค่าเริ่มต้นและใช้operator[]เพื่อแทรกค่าเริ่มต้นนั้น

8
เพื่อให้แม่นยำค่าที่แทรกใหม่คือค่าเริ่มต้น (8.5.5) ดังนั้น: - ถ้า T เป็นประเภทคลาสที่มีตัวสร้างที่ผู้ใช้ประกาศ (12.1) ตัวสร้างเริ่มต้นสำหรับ T จะถูกเรียก (และการเริ่มต้นไม่ถูกต้อง - ขึ้นรูปถ้า T ไม่มีตัวสร้างเริ่มต้นที่สามารถเข้าถึงได้); - ถ้า T เป็นประเภทคลาสที่ไม่ใช่ยูเนี่ยนโดยไม่มีตัวสร้างที่ผู้ใช้ประกาศสมาชิกข้อมูลที่ไม่คงที่และส่วนประกอบพื้นฐานของ T จะถูกกำหนดค่าเริ่มต้น - ถ้า T เป็นประเภทอาร์เรย์แต่ละองค์ประกอบจะถูกกำหนดค่าเริ่มต้น - มิฉะนั้นวัตถุจะเริ่มต้นเป็นศูนย์
Tadeusz Kopec


6

เวอร์ชันทั่วไปเพิ่มเติมรองรับ 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");

MAP :: mapped_type & ไม่ปลอดภัยที่จะส่งคืนเนื่องจาก typename MAP :: mapped_type & defval อาจอยู่นอกขอบเขต
Joe C

5

ไม่มีวิธีใดในการระบุค่าเริ่มต้น - เป็นค่าที่สร้างโดยค่าเริ่มต้นเสมอ (ตัวสร้างพารามิเตอร์เป็นศูนย์)

ในความเป็นจริงoperator[]อาจทำได้มากกว่าที่คุณคาดหวังราวกับว่าไม่มีค่าสำหรับคีย์ที่กำหนดในแผนที่มันจะแทรกค่าใหม่ด้วยค่าจากตัวสร้างเริ่มต้น


2
ถูกต้องเพื่อหลีกเลี่ยงการเพิ่มรายการใหม่ที่คุณสามารถใช้ได้findซึ่งจะคืนค่าตัววนซ้ำท้ายถ้าไม่มีองค์ประกอบสำหรับคีย์ที่กำหนด
Thomas Schaub

@ThomasSchaub ความซับซ้อนของเวลาเท่าไหร่findในกรณีนั้น?
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
จากนั้นแก้ไขเพื่อให้ใช้งานได้ ฉันจะไม่กังวลในการแก้ไขกรณีที่รหัสนี้ไม่ได้ออกแบบมาเพื่อให้สำเร็จ
Thomas Eding

3

ค่าเริ่มต้นโดยใช้ตัวสร้างเริ่มต้นตามที่คำตอบอื่น ๆ กล่าว อย่างไรก็ตามมีประโยชน์ในการเพิ่มว่าในกรณีของประเภทที่เรียบง่าย (ประเภทอินทิกรัลเช่นประเภท 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 หรือไม่ สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับเรื่องนี้


2

วิธีแก้ปัญหาอย่างหนึ่งคือการใช้map::at()แทน[]. หากไม่มีคีย์atให้แสดงข้อยกเว้น แม้จะดีกว่านี้ก็ยังใช้ได้กับเวกเตอร์และเหมาะสำหรับการเขียนโปรแกรมทั่วไปที่คุณสามารถสลับแผนที่กับเวกเตอร์ได้

การใช้ค่าที่กำหนดเองสำหรับคีย์ที่ไม่ได้ลงทะเบียนอาจเป็นอันตรายเนื่องจากค่าที่กำหนดเองนั้น (เช่น -1) อาจถูกประมวลผลเพิ่มเติมในโค้ด ด้วยข้อยกเว้นการตรวจพบจุดบกพร่องได้ง่ายขึ้น


1

บางทีคุณอาจให้ผู้จัดสรรแบบกำหนดเองซึ่งจัดสรรด้วยค่าเริ่มต้นที่คุณต้องการ

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

4
operator[]ส่งคืนวัตถุที่สร้างโดยการเรียกใช้T()ไม่ว่าผู้จัดสรรจะทำอะไรก็ตาม
sbi

1
@sbi: แผนที่ไม่เรียกconstructวิธีการจัดสรรหรือไม่? ฉันคิดว่ามันน่าจะเปลี่ยนได้ ฉันสงสัยว่าconstructฟังก์ชันที่ทำอย่างอื่นที่new(p) T(t);ไม่ใช่รูปแบบที่ดีแม้ว่า แก้ไข: ในการมองย้อนกลับไปที่โง่เขลามิฉะนั้นค่าทั้งหมดจะเหมือนกัน: P กาแฟของฉันอยู่ที่ไหน ...
GManNickG

1
@GMan: สำเนาของฉัน C ++ 03 กล่าวว่า (ใน 23.3.1.2) ที่ผลตอบแทนoperator[] (*((insert(make_pair(x, T()))).first)).secondคำตอบนี้ไม่ถูกต้อง
sbi

คุณพูดถูก แต่ดูเหมือนจะผิดสำหรับฉัน ทำไมพวกเขาไม่ใช้ฟังก์ชันตัวจัดสรรในการแทรก?
VDVLeon

2
@sbi: ไม่ฉันยอมรับว่าคำตอบนี้ผิด แต่ด้วยเหตุผลอื่น คอมไพเลอร์ไม่แน่นอนinsertมีT()แต่แทรกอยู่ภายในเมื่อมันจะใช้หน่วยความจำจัดสรรได้รับสำหรับใหม่Tโทรแล้วในหน่วยความจำที่มีพารามิเตอร์ที่ได้รับซึ่งเป็นconstruct T()ดังนั้นจึงเป็นไปได้ที่จะเปลี่ยนพฤติกรรมoperator[]เพื่อให้มันส่งคืนอย่างอื่น แต่ผู้จัดสรรไม่สามารถแยกความแตกต่างได้ว่าทำไมจึงถูกเรียก ดังนั้นแม้ว่าเราจะconstructเพิกเฉยต่อพารามิเตอร์และใช้ค่าพิเศษของเรานั่นก็หมายความว่าทุกองค์ประกอบที่สร้างขึ้นมีค่านั้นซึ่งไม่ดี
GManNickG

1

ขยายตัวในคำตอบhttps://stackoverflow.com/a/2333816/272642 , แม่แบบนี้ฟังก์ชั่นการใช้งานstd::maps' key_typeและmapped_typetypedefs จะอนุมานชนิดของและ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โดยไม่จำเป็นต้องโยนข้อโต้แย้งเช่น


1

ใช้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_emplaceC

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.