วิธีการเริ่มต้นแผนที่ const คงที่ส่วนตัวใน C ++?


108

ฉันต้องการเพียงแค่พจนานุกรมหรืออาเรย์=>stringint

มีประเภทแผนที่ C ++ สำหรับกรณีนี้

แต่ฉันต้องการอินสแตนซ์สำหรับแผนที่เดียวเท่านั้น (-> คงที่) และแผนที่นี้ไม่สามารถเปลี่ยนแปลงได้ (-> const);

ฉันพบวิธีนี้แล้วกับไลบรารีที่เพิ่มขึ้น

 std::map<int, char> example = 
      boost::assign::map_list_of(1, 'a') (2, 'b') (3, 'c');

มีวิธีอื่นที่ไม่มี lib นี้หรือไม่? ฉันได้ลองสิ่งนี้แล้ว แต่มักจะมีปัญหาบางอย่างเกี่ยวกับการเริ่มต้นแผนที่

class myClass{
private:
    static map<int,int> create_map()
        {
          map<int,int> m;
          m[1] = 2;
          m[3] = 4;
          m[5] = 6;
          return m;
        }
    static map<int,int> myMap =  create_map();

};

1
ประเด็นที่คุณอ้างถึงคืออะไร? คุณกำลังพยายามใช้แผนที่นี้จากตัวแปร / ค่าคงที่ทั่วโลกอื่นหรือไม่?
PéterTörök

นั่นไม่ใช่สตริงอาร์เรย์ที่เชื่อมโยง => int คุณกำลังแมป int กับถ่าน v = k + 'a' - 1.
Johnsyweb

คำตอบ:


107
#include <map>
using namespace std;

struct A{
    static map<int,int> create_map()
        {
          map<int,int> m;
          m[1] = 2;
          m[3] = 4;
          m[5] = 6;
          return m;
        }
    static const map<int,int> myMap;

};

const map<int,int> A:: myMap =  A::create_map();

int main() {
}

3
+1 สำหรับความเรียบง่ายแน่นอนว่าการใช้การBoost.Assignออกแบบที่เหมือนกันก็ค่อนข้างเรียบร้อยเช่นกัน :)
Matthieu M.

5
+1 ขอบคุณ หมายเหตุ: ฉันต้องใส่บรรทัดเริ่มต้นในไฟล์การนำไปใช้งานของฉัน การทิ้งไว้ในไฟล์ส่วนหัวทำให้ฉันมีข้อผิดพลาดเนื่องจากคำจำกัดความหลายรายการ (รหัสเริ่มต้นจะทำงานเมื่อใดก็ตามที่รวมส่วนหัวไว้ที่ใดที่หนึ่ง)
System.Cats.Lol

1
ด้วย g ++ v4.7.3 คอมไพล์นี้จนกว่าฉันจะเพิ่มcout << A::myMap[1];ลงในmain(). มันทำให้เกิดข้อผิดพลาด ข้อผิดพลาดจะไม่เกิดขึ้นหากฉันลบconstคุณสมบัติออกดังนั้นฉันเดาว่าแผนที่operator[]ไม่สามารถจัดการ a const mapอย่างน้อยก็ไม่ใช่ในการใช้งาน g ++ ของไลบรารี C ++
Craig McQueen

2
ข้อผิดพลาดคือ:const_map.cpp:22:23: error: passing ‘const std::map<int, int>’ as ‘this’ argument of ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const key_type&) [with _Key = int; _Tp = int; _Compare = std::less<int>; _Alloc = std::allocator<std::pair<const int, int> >; std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = int; std::map<_Key, _Tp, _Compare, _Alloc>::key_type = int]’ discards qualifiers [-fpermissive]
Craig McQueen

4
อันที่จริงตัวดำเนินการของแผนที่ [] ไม่สามารถทำงานบนแผนที่ const ได้เนื่องจากตัวดำเนินการนั้นสร้างรายการอ้างอิงหากไม่มีอยู่ (เนื่องจากจะส่งคืนการอ้างอิงไปยังค่าที่แมป) C ++ 11 เปิดตัวเมธอด at (KeyValT key) ที่ให้คุณเข้าถึงรายการด้วยคีย์ที่กำหนดโดยจะมีข้อยกเว้นหากไม่มีอยู่ ( en.cppreference.com/w/cpp/container/map/at ) วิธีนี้จะใช้ได้กับอินสแตนซ์ const แต่ไม่สามารถใช้เพื่อแทรกองค์ประกอบบนอินสแตนซ์ที่ไม่ใช่ const ได้ (เช่นเดียวกับตัวดำเนินการ [])
mbargiel

108

มาตรฐาน C ++ 11 แนะนำการเริ่มต้นอย่างสม่ำเสมอซึ่งทำให้ง่ายขึ้นมากหากคอมไพเลอร์ของคุณรองรับ:

//myClass.hpp
class myClass {
  private:
    static map<int,int> myMap;
};


//myClass.cpp
map<int,int> myClass::myMap = {
   {1, 2},
   {3, 4},
   {5, 6}
};

ดูส่วนนี้จาก Professional C ++บน unordered_maps


เราต้องการเครื่องหมายเท่ากับที่ไฟล์ cpp หรือไม่?
phoad

@phoad: เครื่องหมายเท่ากับไม่จำเป็น
Jinxed

ขอบคุณที่แสดงการใช้งาน การทำความเข้าใจวิธีแก้ไขตัวแปรคงที่เป็นประโยชน์มาก
User9102d82

12

ฉันทำแล้ว! :)

ทำงานได้ดีโดยไม่ต้องใช้ C ++ 11

class MyClass {
    typedef std::map<std::string, int> MyMap;

    struct T {
        const char* Name;
        int Num;

        operator MyMap::value_type() const {
            return std::pair<std::string, int>(Name, Num);
        }
    };

    static const T MapPairs[];
    static const MyMap TheMap;
};

const MyClass::T MyClass::MapPairs[] = {
    { "Jan", 1 }, { "Feb", 2 }, { "Mar", 3 }
};

const MyClass::MyMap MyClass::TheMap(MapPairs, MapPairs + 3);

11

หากคุณพบว่าboost::assign::map_list_ofมีประโยชน์ แต่ไม่สามารถใช้งานได้ด้วยเหตุผลบางประการคุณสามารถเขียนของคุณเอง :

template<class K, class V>
struct map_list_of_type {
  typedef std::map<K, V> Map;
  Map data;
  map_list_of_type(K k, V v) { data[k] = v; }
  map_list_of_type& operator()(K k, V v) { data[k] = v; return *this; }
  operator Map const&() const { return data; }
};
template<class K, class V>
map_list_of_type<K, V> my_map_list_of(K k, V v) {
  return map_list_of_type<K, V>(k, v);
}

int main() {
  std::map<int, char> example = 
    my_map_list_of(1, 'a') (2, 'b') (3, 'c');
  cout << example << '\n';
}

เป็นประโยชน์ที่จะทราบว่าสิ่งเหล่านี้ทำงานอย่างไรโดยเฉพาะอย่างยิ่งเมื่อสั้นมาก แต่ในกรณีนี้ฉันจะใช้ฟังก์ชัน:

struct A {
  static map<int, int> const m;
};

a.cpp

namespace {
map<int,int> create_map() {
  map<int, int> m;
  m[1] = 2; // etc.
  return m;
}
}

map<int, int> const A::m = create_map();

6

แนวทางอื่นในการแก้ปัญหา:

struct A {
    static const map<int, string> * singleton_map() {
        static map<int, string>* m = NULL;
        if (!m) {
            m = new map<int, string>;
            m[42] = "42"
            // ... other initializations
        }
        return m;
    }

    // rest of the class
}

สิ่งนี้มีประสิทธิภาพมากขึ้นเนื่องจากไม่มีการคัดลอกแบบหนึ่งจากสแต็กไปยังฮีป (รวมถึงตัวสร้างตัวทำลายในองค์ประกอบทั้งหมด) เรื่องนี้สำคัญหรือไม่ขึ้นอยู่กับกรณีการใช้งานของคุณ ไม่สำคัญกับสตริง! (แต่คุณอาจพบหรือไม่พบเวอร์ชันนี้ "สะอาดกว่า")


3
RVO กำจัดการคัดลอกในเหมืองและคำตอบของนีล

6

หากแผนที่ต้องมีเฉพาะรายการที่ทราบในเวลาคอมไพล์และคีย์ของแผนที่เป็นจำนวนเต็มคุณก็ไม่จำเป็นต้องใช้แผนที่เลย

char get_value(int key)
{
    switch (key)
    {
        case 1:
            return 'a';
        case 2:
            return 'b';
        case 3:
            return 'c';
        default:
            // Do whatever is appropriate when the key is not valid
    }
}

5
+1 เพื่อชี้ให้เห็นว่าไม่จำเป็นต้องใช้แผนที่อย่างไรก็ตามคุณไม่สามารถทำซ้ำได้
Viktor Sehr

4
นั่นswitchเป็นเรื่องที่แย่มาก ทำไมไม่return key + 'a' - 1?
Johnsyweb

12
@Johnsyweb. ฉันคิดว่าการทำแผนที่ที่ผู้โพสต์ต้นฉบับนำเสนอเป็นเพียงตัวอย่างเท่านั้นและไม่ได้บ่งบอกถึงการทำแผนที่จริงที่เขามี ดังนั้นฉันจะถือว่าreturn key + 'a' - 1มันใช้ไม่ได้กับการทำแผนที่จริงของเขา
Matthew T.Staebler

3

คุณสามารถลองสิ่งนี้:

MyClass.h

class MyClass {
private:
    static const std::map<key, value> m_myMap; 
    static const std::map<key, value> createMyStaticConstantMap();
public:
    static std::map<key, value> getMyConstantStaticMap( return m_myMap );
}; //MyClass

MyClass.cpp

#include "MyClass.h"

const std::map<key, value> MyClass::m_myMap = MyClass::createMyStaticConstantMap();

const std::map<key, value> MyClass::createMyStaticConstantMap() {
    std::map<key, value> mMap;
    mMap.insert( std::make_pair( key1, value1 ) );
    mMap.insert( std::make_pair( key2, value2 ) );
    // ....
    mMap.insert( std::make_pair( lastKey, lastValue ) ); 
    return mMap;
} // createMyStaticConstantMap

ด้วยการใช้งานนี้แผนที่คงที่คลาสของคุณเป็นสมาชิกส่วนตัวและสามารถเข้าถึงคลาสอื่น ๆ โดยใช้วิธีรับสาธารณะ มิฉะนั้นเนื่องจากค่าคงที่และไม่สามารถเปลี่ยนแปลงได้คุณสามารถลบเมธอด public get และย้ายตัวแปรแผนที่ไปยังส่วน public ของคลาสได้ อย่างไรก็ตามฉันจะปล่อยให้เมธอด createMap เป็นส่วนตัวหรือได้รับการป้องกันหากจำเป็นต้องมีการสืบทอดและหรือความหลากหลาย นี่คือตัวอย่างการใช้งานบางส่วน

 std::map<key,value> m1 = MyClass::getMyMap();
 // then do work on m1 or
 unsigned index = some predetermined value
 MyClass::getMyMap().at( index ); // As long as index is valid this will 
 // retun map.second or map->second value so if in this case key is an
 // unsigned and value is a std::string then you could do
 std::cout << std::string( MyClass::getMyMap().at( some index that exists in map ) ); 
// and it will print out to the console the string locted in the map at this index. 
//You can do this before any class object is instantiated or declared. 

 //If you are using a pointer to your class such as:
 std::shared_ptr<MyClass> || std::unique_ptr<MyClass>
 // Then it would look like this:
 pMyClass->getMyMap().at( index ); // And Will do the same as above
 // Even if you have not yet called the std pointer's reset method on
 // this class object. 

 // This will only work on static methods only, and all data in static methods must be available first.

ฉันได้แก้ไขโพสต์เดิมของฉันแล้วไม่มีอะไรผิดปกติกับโค้ดดั้งเดิมที่ฉันโพสต์เพื่อคอมไพล์สร้างและรันอย่างถูกต้องมันเป็นเพียงเวอร์ชันแรกของฉันที่ฉันนำเสนอเป็นคำตอบแผนที่ถูกประกาศเป็นสาธารณะและแผนที่นั้น const แต่ไม่คงที่


2

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

std::map<int, int> m = [] () {
    std::pair<int,int> _m[] = {
        std::make_pair(1 , sizeof(2)),
        std::make_pair(3 , sizeof(4)),
        std::make_pair(5 , sizeof(6))};
    std::map<int, int> m;
    for (auto data: _m)
    {
        m[data.first] = data.second;
    }
    return m;
}();

0

การเรียกใช้ฟังก์ชันไม่สามารถปรากฏในนิพจน์คงที่

ลองสิ่งนี้: (เป็นเพียงตัวอย่าง)

#include <map>
#include <iostream>

using std::map;
using std::cout;

class myClass{
 public:
 static map<int,int> create_map()
    {
      map<int,int> m;
      m[1] = 2;
      m[3] = 4;
      m[5] = 6;
      return m;
    }
 const static map<int,int> myMap;

};
const map<int,int>myClass::myMap =  create_map();

int main(){

   map<int,int> t=myClass::create_map();
   std::cout<<t[1]; //prints 2
}

6
ฟังก์ชันสามารถใช้เพื่อเริ่มต้นวัตถุ const ได้อย่างแน่นอน

ในรหัสของ OP static map<int,int> myMap = create_map();ไม่ถูกต้อง
Prasoon Saurav

3
รหัสในคำถามไม่ถูกต้องเราทุกคนเห็นด้วย แต่ไม่มีส่วนเกี่ยวข้องกับ 'นิพจน์คงที่' ตามที่คุณพูดในคำตอบนี้ แต่ด้วยความจริงที่ว่าคุณสามารถเริ่มต้นสมาชิกคงที่ของคลาสใน การประกาศว่าเป็นประเภทจำนวนเต็มหรือ enum สำหรับประเภทอื่น ๆ ทั้งหมดการกำหนดค่าเริ่มต้นจะต้องทำในนิยามสมาชิกไม่ใช่การประกาศ
David Rodríguez - dribeas

คำตอบของนีลรวบรวมด้วย g ++ ถึงกระนั้นฉันจำได้ว่ามีปัญหาบางอย่างกับแนวทางนี้ใน GNU toolchain เวอร์ชันก่อนหน้า มีคำตอบที่ถูกต้องสากลหรือไม่?
Basilevs

1
@Prasoon: ฉันไม่รู้ว่าคอมไพเลอร์พูดว่าอะไร แต่ข้อผิดพลาดในรหัสคำถามคือการเริ่มต้นแอตทริบิวต์สมาชิกคงที่ของประเภทคลาสในการประกาศคลาสไม่ว่าการเริ่มต้นจะเป็นนิพจน์คงที่หรือไม่ก็ตาม หากคุณกำหนดคลาส: struct testdata { testdata(int){} }; struct test { static const testdata td = 5; }; testdata test::td;คอมไพล์จะล้มเหลวแม้ว่าการเริ่มต้นจะดำเนินการด้วยนิพจน์คงที่ ( 5) นั่นคือ 'นิพจน์คงที่' ไม่เกี่ยวข้องกับความถูกต้อง (หรือขาด) ของรหัสเริ่มต้น
David Rodríguez - dribeas

-2

ฉันมักจะใช้รูปแบบนี้และแนะนำให้คุณใช้ด้วย:

class MyMap : public std::map<int, int>
{
public:
    MyMap()
    {
        //either
        insert(make_pair(1, 2));
        insert(make_pair(3, 4));
        insert(make_pair(5, 6));
        //or
        (*this)[1] = 2;
        (*this)[3] = 4;
        (*this)[5] = 6;
    }
} const static my_map;

แน่นอนว่ามันไม่สามารถอ่านได้มากนัก แต่ถ้าไม่มี libs อื่น ๆ จะเป็นการดีที่สุดที่เราทำได้ นอกจากนี้จะไม่มีการดำเนินการซ้ำซ้อนเช่นการคัดลอกจากแผนที่หนึ่งไปยังอีกแผนที่หนึ่งเหมือนในความพยายามของคุณ

สิ่งนี้มีประโยชน์มากยิ่งขึ้นภายในฟังก์ชัน: แทนที่จะเป็น:

void foo()
{
   static bool initComplete = false;
   static Map map;
   if (!initComplete)
   {
      initComplete = true;
      map= ...;
   }
}

ใช้สิ่งต่อไปนี้:

void bar()
{
    struct MyMap : Map
    {
      MyMap()
      {
         ...
      }
    } static mymap;
}

ไม่เพียง แต่คุณไม่ต้องการที่นี่เพื่อจัดการกับตัวแปรบูลีนอีกต่อไปคุณจะไม่มีตัวแปรส่วนกลางที่ซ่อนอยู่ซึ่งถูกตรวจสอบว่ามีการเรียกใช้ initializer ของตัวแปรคงที่ภายในฟังก์ชันแล้วหรือไม่


6
การสืบทอดควรเป็นเครื่องมือของทางเลือกสุดท้ายไม่ใช่อย่างแรก

คอมไพเลอร์ที่รองรับ RVO กำจัดการคัดลอกซ้ำซ้อนกับเวอร์ชันของฟังก์ชัน C ++ 0x move semantics จะกำจัดส่วนที่เหลือเมื่อพร้อมใช้งาน ไม่ว่าในกรณีใดฉันสงสัยว่ามันใกล้จะเป็นคอขวด

โรเจอร์ฉันรู้ดีเกี่ยวกับ RVO, && และย้ายความหมาย นี่เป็นวิธีแก้ปัญหาสำหรับโค้ดและเอนทิตีในปริมาณที่น้อยที่สุด นอกจากนี้คุณลักษณะ C ++ 0x ทั้งหมดจะไม่ช่วยในตัวอย่างฟังก์ชันวัตถุคงที่เนื่องจากเราไม่ได้รับอนุญาตให้กำหนดฟังก์ชันภายในฟังก์ชัน
Pavel Chikulaev
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.