মানচিত্রে সন্নিবেশ করানোর জন্য পছন্দের / আইডিয়োমেটিক উপায় কী?


111

আমি একটিতে উপাদান সন্নিবেশ করার বিভিন্ন চারটি উপায় চিহ্নিত করেছি std::map:

std::map<int, int> function;

function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));

এর মধ্যে কোনটি পছন্দসই / প্রতিচ্ছবিযুক্ত উপায়? (এবং অন্য কোনও উপায় কি আমি ভেবে দেখিনি?)


26
আপনার মানচিত্রে "ফাংশন" নয়, "উত্তর" বলা উচিত
ভিনসেন্ট রবার্ট

2
@ ভিনসেন্ট: এইচএম? একটি ফাংশন মূলত দুটি সেট এর মধ্যে একটি মানচিত্র।
ফ্রেডওভারফ্লো

7
@ ফ্রেড ওভারফ্লো: ভিনসেন্টের মন্তব্যটি নির্দিষ্ট বইয়ের জন্য মজার রসিকতা বলে মনে হচ্ছে ...
ভিক্টর সোরোকিন

1
এটি মূলটির বিরোধিতা বলে মনে হচ্ছে - 42 এক সাথে (ক) জীবন, মহাবিশ্ব এবং সমস্ত কিছুর উত্তর হতে পারে না এবং (খ) কিছুই নয় nothing কিন্তু তারপরে আপনি কীভাবে জীবন, মহাবিশ্ব এবং সমস্ত কিছুর পরিচয় হিসাবে প্রকাশ করবেন?
স্টুয়ার্ট গোলোডেটজ

19
@ sgolodetz আপনি যথেষ্ট পরিমাণে ইন্ট্রাস্ট দিয়ে সমস্ত প্রকাশ করতে পারেন।
ইয়াকভ গালকা

উত্তর:


90

প্রথমত operator[]এবং insertসদস্য ফাংশনগুলি কার্যত সমতুল্য নয়:

  • operator[]হবে অনুসন্ধান কীয়ের জন্য, একটি সন্নিবেশ ডিফল্ট নির্মাণ মান যদি পাওয়া যায়নি, এবং একটি রেফারেন্স যার প্রতি তুমি একটি মান নির্ধারণ ফিরে যান। স্পষ্টতই, এটি অকার্যকর হতে পারে যদি mapped_typeডিফল্ট নির্মিত এবং নির্ধারিত পরিবর্তে সরাসরি আরম্ভ করা থেকে সুবিধা উপভোগ করতে পারে । এই পদ্ধতিটি এটি নির্ধারণ করাও অসম্ভব করে দেয় যে কোনও সন্নিবেশ প্রকৃতপক্ষে ঘটেছে কিনা বা যদি আপনি কেবল আগে সন্নিবেশিত কীটির জন্য মানটি ওভাররাইট করে থাকেন
  • insertযদি কোডটি ইতিমধ্যে মানচিত্র উপস্থিত সদস্য ফাংশন কোনো প্রভাব থাকে এবং আপনি হবে যদিও এটা প্রায়ই ভুলে যাওয়া, ফেরৎ একটি std::pair<iterator, bool>যা আগ্রহের হতে পারে (এর মধ্যে উল্লেখযোগ্য হল কিনা তা নির্ধারণ করতে সন্নিবেশ আসলে সম্পন্ন করা হয়েছে)।

কল করার জন্য তালিকাভুক্ত সমস্ত সম্ভাবনা থেকে insert, তিনটিই প্রায় সমান। অনুস্মারক হিসাবে, আসুন insertস্ট্যান্ডার্ডটিতে স্বাক্ষরটি দেখুন :

typedef pair<const Key, T> value_type;

  /* ... */

pair<iterator, bool> insert(const value_type& x);

তাহলে তিনটি কল কীভাবে আলাদা?

  • std::make_pairটেমপ্লেট যুক্তি সিদ্ধান্তগ্রহণ উপর নির্ভর এবং পারা (এবং এই ক্ষেত্রে হবে ) প্রকৃত তুলনায় বিভিন্ন ধরনের কিছু উত্পাদন value_typeমানচিত্রের, যা একটি অতিরিক্ত কল প্রয়োজন হবে std::pairরূপান্তর অনুক্রমে টেমপ্লেট কন্সট্রাকটর value_type(অর্থাৎ, যোগ constকরার first_type)
  • std::pair<int, int>std::pairপ্যারামিটারে রূপান্তর করতে value_type(যেমন: যোগ constকরা first_type) টেমপ্লেট নির্মাণকারীকে অতিরিক্ত কলেরও প্রয়োজন হবে
  • std::map<int, int>::value_typeসন্দেহের পক্ষে একেবারেই কোনও জায়গা নেই কারণ এটি সরাসরি insertসদস্য কার্য দ্বারা প্রত্যাশিত প্যারামিটার ধরণের type

শেষ অবধি, আমি operator[]উদ্দেশ্যটি সন্নিবেশ করানো কখনই ব্যবহার করা এড়াতে পারি , যদি না ডিফল্ট-নির্মাণ ও নির্ধারণের জন্য অতিরিক্ত ব্যয় না হয় এবং mapped_typeকোনও নতুন কী কার্যকরভাবে প্রবেশ করানো হয়েছে কিনা তা নির্ধারণের বিষয়ে আমি যত্নশীল নই। ব্যবহার করার সময় insertএকটি নির্মাণ value_typeকরা সম্ভবত যাওয়ার উপায়।


Make_pair () তে কী থেকে কনস্টে রূপান্তর কী সত্যিই অন্য ফাংশন কলের জন্য অনুরোধ করে? মনে হচ্ছে কোনও অন্তর্নিহিত কাস্ট যথেষ্ট হবে যা কোন সংকলক তা করতে খুশি হওয়া উচিত।
গ্যালাকটিকা

99

সি ++ 11 হিসাবে আপনার কাছে দুটি বড় অতিরিক্ত বিকল্প রয়েছে। প্রথমত, আপনি insert()তালিকা আরম্ভের বাক্য গঠন সহ ব্যবহার করতে পারেন :

function.insert({0, 42});

এটি কার্যত সমান

function.insert(std::map<int, int>::value_type(0, 42));

তবে আরও অনেক সংক্ষিপ্ত এবং পাঠযোগ্য। অন্যান্য উত্তরে যেমন উল্লেখ করা হয়েছে, অন্যান্য ফর্মগুলির তুলনায় এর বেশ কয়েকটি সুবিধা রয়েছে:

  • operator[]পদ্ধতি, হস্তান্তরযোগ্য হতে ম্যাপ টাইপ প্রয়োজন যা সবসময় তা হয় না।
  • operator[]পদ্ধতির বিদ্যমান উপাদান ওভাররাইট, এবং আপনি বলতে কোন উপায় এই ঘটেছে কিনা দেয় পারবেন না।
  • insertআপনি যে তালিকাভুক্ত করেছেন তার অন্যান্য ফর্মগুলিতে একটি অন্তর্নিহিত ধরণের রূপান্তর জড়িত যা আপনার কোডকে ধীর করে দিতে পারে।

প্রধান অসুবিধাটি হ'ল এই ফর্মটি কী এবং মানটি অনুলিপি করার জন্য প্রয়োজন ছিল তাই এটি unique_ptrমান সহ উদাহরণস্বরূপ কোনও মানচিত্রের সাথে কাজ করবে না । এটি স্ট্যান্ডার্ডে স্থির করা হয়েছে, তবে ঠিক করাটি এখনও আপনার স্ট্যান্ডার্ড লাইব্রেরি বাস্তবায়নে পৌঁছেছে না।

দ্বিতীয়ত, আপনি emplace()পদ্ধতিটি ব্যবহার করতে পারেন :

function.emplace(0, 42);

এটি যেকোন ধরণের রূপের চেয়ে আরও সংক্ষিপ্ত insert(), কেবল চলার মতো প্রকারের সাথে সূক্ষ্মভাবে কাজ করে unique_ptrএবং তাত্ত্বিকভাবে কিছুটা দক্ষ হতে পারে (যদিও একটি শালীন সংকলক পার্থক্যটি সরিয়ে নিতে পারে)। একমাত্র প্রধান ত্রুটিটি এটি আপনার পাঠকদের কিছুটা অবাক করে দিতে পারে, যেহেতু emplaceসাধারণত পদ্ধতিগুলি সেভাবে ব্যবহার করা হয় না।



11

প্রথম সংস্করণ:

function[0] = 42; // version 1

মানচিত্রে 42 মান সন্নিবেশ করানো বা নাও পারে। যদি কীটি 0বিদ্যমান থাকে, তবে এটি সেই কীটিকে ৪২ টি বরাদ্দ করবে, যে কীটির যা মান ছিল তা ওভাররাইট করে। অন্যথায় এটি কী / মান জুড়ি .োকায়।

সন্নিবেশ ফাংশন:

function.insert(std::map<int, int>::value_type(0, 42));  // version 2
function.insert(std::pair<int, int>(0, 42));             // version 3
function.insert(std::make_pair(0, 42));                  // version 4

অন্যদিকে, 0মানচিত্রটিতে কীটি ইতিমধ্যে উপস্থিত থাকলে কিছু করবেন না । যদি কীটি বিদ্যমান না থাকে তবে এটি কী / মান জোড়াটি সন্নিবেশ করায়।

তিনটি সন্নিবেশ ফাংশন প্রায় অভিন্ন। std::map<int, int>::value_typeহয় typedefজন্য std::pair<const int, int>, এবং std::make_pair()সম্ভবত উৎপন্ন std::pair<>টেমপ্লেট সিদ্ধান্তগ্রহণ জাদু মাধ্যমে। তবে শেষ ফলাফলটি 2, 3 এবং 4 সংস্করণের জন্য একই হওয়া উচিত।

আমি কোনটি ব্যবহার করব? আমি ব্যক্তিগতভাবে সংস্করণ 1 পছন্দ করি; এটি সংক্ষিপ্ত এবং "প্রাকৃতিক"। অবশ্যই, যদি এটির ওভারর্টিং আচরণটি পছন্দসই না হয়, তবে আমি সংস্করণ 4 পছন্দ করি, কারণ এটির সংস্করণ 2 এবং 3 এর চেয়ে কম টাইপ করা দরকার, আমি জানি না একটিতে কী / মান জোড়া সন্নিবেশ করার একক ডি ফ্যাক্টো উপায় আছে কিনা? std::map

এর একটি নির্মাতার মাধ্যমে মানচিত্রে মান সন্নিবেশ করার আর একটি উপায়:

std::map<int, int> quadratic_func;

quadratic_func[0] = 0;
quadratic_func[1] = 1;
quadratic_func[2] = 4;
quadratic_func[3] = 9;

std::map<int, int> my_func(quadratic_func.begin(), quadratic_func.end());

5

যদি আপনি কী 0 দিয়ে উপাদানটি ওভাররাইট করতে চান

function[0] = 42;

অন্যথায়:

function.insert(std::make_pair(0, 42));

5

যেহেতু সি ++ 17 std::map দুটি নতুন সন্নিবেশ পদ্ধতির প্রস্তাব দেয়: insert_or_assign()এবং try_emplace(), যেমন স্পাইপ্যানডির মন্তব্যেও উল্লেখ করা হয়েছে ।

insert_or_assign()

মূলত, insert_or_assign()এর একটি "উন্নত" সংস্করণ operator[]। বিপরীতে operator[], insert_or_assign()মানচিত্রের মান টাইপ প্রয়োজন হয় না ডিফল্ট অঙ্কনযোগ্য যাবে। উদাহরণস্বরূপ, নিম্নলিখিত কোডটি সংকলন করে না, কারণ MyClassকোনও ডিফল্ট নির্মাতা নেই:

class MyClass {
public:
    MyClass(int i) : m_i(i) {};
    int m_i;
};

int main() {
    std::map<int, MyClass> myMap;

    // VS2017: "C2512: 'MyClass::MyClass' : no appropriate default constructor available"
    // Coliru: "error: no matching function for call to 'MyClass::MyClass()"
    myMap[0] = MyClass(1);

    return 0;
}

তবে, আপনি যদি myMap[0] = MyClass(1);নিম্নলিখিত লাইনটি দ্বারা প্রতিস্থাপন করেন, তবে কোডগুলি সংকলন এবং সন্নিবেশটি যেমন ইচ্ছা তেমন ঘটে:

myMap.insert_or_assign(0, MyClass(1));

তদুপরি, অনুরূপ insert(), insert_or_assign()a প্রদান করে pair<iterator, bool>। বুলিয়ান মান হ'ল trueযদি কোনও সন্নিবেশ ঘটে এবং falseযদি কোনও কার্য সম্পাদন হয়। পুনরুক্তি করা উপাদানটি sertedোকানো বা আপডেট করা হয়েছে to

try_emplace()

উপরের মতো, try_emplace()এর একটি "উন্নতি" emplace()। এর বিপরীতে emplace(), try_emplace()মানচিত্রটিতে ইতিমধ্যে বিদ্যমান একটি কী কারণে সন্নিবেশ ব্যর্থ হলে এর যুক্তিগুলি সংশোধন করবেন না। উদাহরণস্বরূপ, নিম্নলিখিত কোড মানচিত্রে ইতিমধ্যে সঞ্চিত (কী * দেখুন) একটি কী দিয়ে একটি উপাদানকে স্থান দেওয়ার চেষ্টা করেছে:

int main() {
    std::map<int, std::unique_ptr<MyClass>> myMap2;
    myMap2.emplace(0, std::make_unique<MyClass>(1));

    auto pMyObj = std::make_unique<MyClass>(2);    
    auto [it, b] = myMap2.emplace(0, std::move(pMyObj));  // *

    if (!b)
        std::cout << "pMyObj was not inserted" << std::endl;

    if (pMyObj == nullptr)
        std::cout << "pMyObj was modified anyway" << std::endl;
    else
        std::cout << "pMyObj.m_i = " << pMyObj->m_i <<  std::endl;

    return 0;
}

আউটপুট (কমপক্ষে ভিএস2017 এবং কলিরুর জন্য):

pMyObj sertedোকানো হয়নি
pMyObj যাইহোক পরিবর্তিত হয়েছিল

আপনি দেখতে পাচ্ছেন যে, pMyObjআর আসল বস্তুটির দিকে আর নির্দেশ করে না। তবে, আপনি যদি auto [it, b] = myMap2.emplace(0, std::move(pMyObj));নিম্নলিখিত কোড দ্বারা প্রতিস্থাপন করেন , তবে আউটপুটটি অন্যরকম দেখাচ্ছে কারণ pMyObjঅপরিবর্তিত রয়েছে:

auto [it, b] = myMap2.try_emplace(0, std::move(pMyObj));

আউটপুট:

pMyObj
sertedোকানো হয়নি pMyObj pMyObj.m_i = 2

কলিরুতে কোড

দয়া করে নোট করুন: আমি আমার উত্তরগুলি যথাযথভাবে সংক্ষিপ্ত এবং সরল রাখার চেষ্টা করেছি যাতে এই উত্তরটিতে ফিট করে। আরও সুনির্দিষ্ট এবং বিস্তৃত বিবরণের জন্য, আমি সাবলীল সি ++ তে এই নিবন্ধটি পড়ার পরামর্শ দিই ।


3

আমি উপরে উল্লিখিত সংস্করণগুলির মধ্যে কিছু সময়ের তুলনা চালিয়ে যাচ্ছি:

function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));

সন্নিবেশিত সংস্করণগুলির মধ্যে সময়ের পার্থক্যটি ক্ষুদ্রতর হয়।

#include <map>
#include <vector>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;
class Widget {
public:
    Widget() {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = 1.0;
        }
    }
    Widget(double el)   {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = el;
        }
    }
private:
    std::vector<double> m_vec;
};


int main(int argc, char* argv[]) {



    std::map<int,Widget> map_W;
    ptime t1 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
    }
    ptime t2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff = t2 - t1;
    std::cout << diff.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_2;
    ptime t1_2 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_2.insert(std::make_pair(it,Widget(2.0)));
    }
    ptime t2_2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_2 = t2_2 - t1_2;
    std::cout << diff_2.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_3;
    ptime t1_3 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_3[it] = Widget(2.0);
    }
    ptime t2_3 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_3 = t2_3 - t1_3;
    std::cout << diff_3.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_0;
    ptime t1_0 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
    }
    ptime t2_0 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_0 = t2_0 - t1_0;
    std::cout << diff_0.total_milliseconds() << std::endl;

    system("pause");
}

এটি যথাক্রমে সংস্করণগুলির জন্য দেয় (আমি ফাইলটি 3 বার চালিয়েছি, সুতরাং প্রতিটিটির জন্য পরপর 3 বারের পার্থক্য):

map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));

2198 এমএস, 2078 এমএস, 2072 এমএস

map_W_2.insert(std::make_pair(it,Widget(2.0)));

2290 এমএস, 2037 এমএস, 2046 এমএস

 map_W_3[it] = Widget(2.0);

2592 এমএস, 2278 এমএস, 2296 এমএস

 map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));

2234 এমএস, 2031 এমএস, 2027 এমএস

সুতরাং, বিভিন্ন differentোকানো সংস্করণের মধ্যে ফলাফল উপেক্ষা করা যেতে পারে (যদিও একটি অনুমান পরীক্ষা করা হয়নি)!

map_W_3[it] = Widget(2.0);সংস্করণ উইজেট জন্য ডিফল্ট কন্সট্রাকটর সঙ্গে একটি আরম্ভের কারণে এই উদাহরণস্বরূপ 10-15% বেশি সময় লাগে।


2

সংক্ষেপে, []অপারেটর মানগুলি আপডেট করার জন্য আরও দক্ষ কারণ এটিতে মান ধরণের ডিফল্ট কনস্ট্রাক্টরকে কল করা এবং তারপরে এটি একটি নতুন মান নির্ধারিত হয়, যখন insert()মানগুলি যুক্ত করার জন্য আরও দক্ষ।

কার্যকর এসটিএল থেকে উদ্ধৃত স্নিপেট : স্কট মায়ার্স দ্বারা স্ট্যান্ডার্ড টেম্পলেট লাইব্রেরির আপনার ব্যবহারের উন্নতি করার 50 টি নির্দিষ্ট উপায় , আইটেম 24 সাহায্য করতে পারে।

template<typename MapType, typename KeyArgType, typename ValueArgType>
typename MapType::iterator
insertKeyAndValue(MapType& m, const KeyArgType&k, const ValueArgType& v)
{
    typename MapType::iterator lb = m.lower_bound(k);

    if (lb != m.end() && !(m.key_comp()(k, lb->first))) {
        lb->second = v;
        return lb;
    } else {
        typedef typename MapType::value_type MVT;
        return m.insert(lb, MVT(k, v));
    }
}

আপনি এর জেনেরিক-প্রোগ্রামিং-মুক্ত সংস্করণ চয়ন করার সিদ্ধান্ত নিতে পারেন তবে মূল বিষয়টি হ'ল আমি এই দৃষ্টান্তটি ('অ্যাড' এবং 'আপডেট' পার্থক্যমূলক) অত্যন্ত দরকারী বলে মনে করি।


1

আপনি যদি এসটিডি :: ম্যাপে উপাদানটি সন্নিবেশ করতে চান - সন্নিবেশ () ফাংশনটি ব্যবহার করুন এবং যদি আপনি উপাদানটি (কী দ্বারা) সন্ধান করতে চান এবং এতে কিছু বরাদ্দ করতে চান - অপারেটর ব্যবহার করুন []।

সরলকরণের জন্য ব্যবহারের বুস্ট :: এইরকম লাইব্রেরি বরাদ্দ করুন:

using namespace boost::assign;

// For inserting one element:

insert( function )( 0, 41 );

// For inserting several elements:

insert( function )( 0, 41 )( 0, 42 )( 0, 43 );

1

Sertোকানোর আরেকটি আগ্রহ দেখানোর জন্য আমি সমস্যাটি কিছুটা (স্ট্রিংয়ের মানচিত্র) পরিবর্তন করেছি:

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

rancking[0] = 42;  // << some compilers [gcc] show no error

rancking.insert(std::pair<int, std::string>(0, 42));// always a compile error

কম্পাইলারটি "র‌্যাঙ্কিং [1] = 42" এ কোনও ত্রুটি দেখায় না এ বিষয়টি সত্য; ধ্বংসাত্মক প্রভাব ফেলতে পারে!


সংঘটিতকারীরা প্রাক্তনটির জন্য একটি ত্রুটি দেখায় না কারণ std::string::operator=(char)উপস্থিত রয়েছে, তবে তারা পরেরটির জন্য ত্রুটি দেখায় কারণ নির্মাণকারীর std::string::string(char)অস্তিত্ব নেই। এটি একটি ত্রুটি তৈরি করা উচিত নয় কারণ সি ++ সর্বদা নিখরচায়ভাবে কোনও পূর্ণসংখ্যার-শৈলীর আক্ষরিক হিসাবে স্বতন্ত্রভাবে ব্যাখ্যা করে char, সুতরাং এটি কোনও সংকলক বাগ নয়, বরং এটি একটি প্রোগ্রামার ত্রুটি। মূলত, আমি কেবল বলছি যে এটি আপনার কোডটিতে একটি বাগ প্রবর্তন করে কিনা তা আপনাকে নিজের জন্য নজর রাখা উচিত। BTW, আপনি মুদ্রণ করতে পারেন rancking[0]এবং একটি কম্পাইলার হওয়া ASCII ইচ্ছা আউটপুট ব্যবহার *, যা (char)(42)
কিথ এম

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.