c++ - programming - জাভা প্রোগ্রামার



সি++ মানচিত্রে বনাম emplace vs অপারেটর[] সন্নিবেশ করান (3)

Emplace: আপনি ইতিমধ্যে তৈরি করা হয়েছে যে প্রকৃত বস্তু ব্যবহার করতে rvalue রেফারেন্স সুবিধা নেয়। এর মানে হল কোন কপি বা সরানো কনস্ট্রাকটর বলা হয় না, বড় বস্তুর জন্য ভাল! হে (লগ (এন)) সময়।

সন্নিবেশ করান: স্ট্যান্ডার্ড তরল রেফারেন্স এবং রেভ্যুই রেফারেন্সের জন্য ওভারলোডগুলি রয়েছে, পাশাপাশি ইথারেটরের উপাদানগুলি তালিকাভুক্ত করার তালিকা রয়েছে এবং একটি উপাদান সম্পর্কিত অবস্থান হিসাবে "ইঙ্গিত" রয়েছে। একটি "ইঙ্গিত" ইটারারেটরের ব্যবহার সময় সন্নিবেশ সময়কে সংকুচিত করে নিতে পারে, অন্যথায় এটি O (লগ (N)) সময়।

অপারেটর []: বস্তু বিদ্যমান কিনা তা পরীক্ষা করে দেখায় এবং যদি এটি থাকে তবে এই বস্তুর রেফারেন্সটি সংশোধন করে অন্যথায় বস্তুর উপর make_pair কল করতে প্রদত্ত কী এবং মান ব্যবহার করে এবং তারপর সন্নিবেশ ফাংশন হিসাবে একই কাজ করে। এই হে (লগ (এন)) সময়।

make_pair: একটি জোড়া করতে চেয়ে একটু বেশি করে।

স্ট্যান্ডার্ড emplace যোগ করার জন্য কোন "প্রয়োজন" ছিল। সি ++ 11 এ আমি বিশ্বাস করি & রেফারেন্স টাইপ রেফারেন্স যোগ করা হয়েছে। এটি সরানো স্যাম্যান্টিক্সের জন্য প্রয়োজনীয়তা সরানো হয়েছে, এবং কিছু নির্দিষ্ট ধরণের মেমরি পরিচালনার অপ্টিমাইজেশান অনুমোদিত। বিশেষ করে, Rvalue রেফারেন্স। ওভারলোড করা সন্নিবেশ (value_type &&) অপারেটর in_place সেমিক্যান্টগুলির সুবিধা গ্রহণ করে না এবং তাই এটি খুব কম কার্যকর। যদিও এটি র্যালু রেফারেন্সগুলির সাথে ডিল করার ক্ষমতা সরবরাহ করে, তবে এটি তাদের মূল উদ্দেশ্যকে উপেক্ষা করে, যা বস্তুর নির্মাণের ক্ষেত্রে।

https://src-bin.com

আমি প্রথমবারের মত মানচিত্র ব্যবহার করছি এবং আমি বুঝতে পেরেছি যে একটি উপাদান সন্নিবেশ করার অনেক উপায় রয়েছে। আপনি emplace() , operator[] বা insert() , এবং আরও ভেরিয়েন্টগুলি ব্যবহার করতে পারেন যেমন make_pair বা make_pair । তাদের সকলের সম্পর্কে অনেক তথ্য এবং বিশেষ ক্ষেত্রে প্রশ্ন থাকলেও আমি এখনও বড় ছবিটি বুঝতে পারছি না। সুতরাং, আমার দুটি প্রশ্ন হল:

  1. অন্যদের উপর তাদের প্রতিটি সুবিধা কি?

  2. স্ট্যান্ডার্ড emplace যোগ করার জন্য কোন প্রয়োজন ছিল? এটা ছাড়া আগে সম্ভব ছিল না যে কিছু আছে?


Answer #1

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

এখানে প্রদর্শিত একটি উদাহরণ:

#include <vector>

struct foo
{
    explicit foo(int);
};

int main()
{
    std::vector<foo> v;

    v.emplace(v.end(), 10);      // Works
    //v.insert(v.end(), 10);     // Error, not explicit
    v.insert(v.end(), foo(10));  // Also works
}

এটি স্বীকার্যক্রমে একটি খুব নির্দিষ্ট বিবরণ, তবে যখন আপনি ব্যবহারকারী-সংজ্ঞায়িত রূপান্তরগুলির শৃঙ্খলা নিয়ে কাজ করছেন, তখন এটি মনে রাখার পক্ষে মূল্যবান।


Answer #2

নিম্নলিখিত কোডটি কীভাবে insert() emplace() থেকে পৃথক করে emplace() "বড় ছবি ধারণা" বুঝতে সহায়তা করতে পারে:

#include <iostream>
#include <unordered_map>
#include <utility>

struct Foo {
  static int foo_counter; //Track how many Foo objects have been created.
  int val; //This Foo object was the val-th Foo object to be created.

  Foo() { val = foo_counter++;
    std::cout << "Foo() with val:                " << val << '\n';
  }
  Foo(int value) : val(value) { foo_counter++;
    std::cout << "Foo(int) with val:             " << val << '\n';
  }
  Foo(Foo& f2) { val = foo_counter++;
    std::cout << "Foo(Foo &) with val:           " << val
              << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(const Foo& f2) { val = foo_counter++;
    std::cout << "Foo(const Foo &) with val:     " << val
              << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(Foo&& f2) { val = foo_counter++;
    std::cout << "Foo(Foo&&) moving:             " << f2.val
              << " \tand changing it to:\t" << val << '\n';
  }
  ~Foo() { std::cout << "~Foo() destroying:             " << val << '\n'; }

  Foo& operator=(const Foo& rhs) {
    std::cout << "Foo& operator=(const Foo& rhs) with rhs.val: " << rhs.val
              << " \tcalled with lhs.val = \t" << val
              << " \tChanging lhs.val to: \t" << rhs.val << '\n';
    val = rhs.val;
    return *this;
  }

  bool operator==(const Foo &rhs) const { return val == rhs.val; }
  bool operator<(const Foo &rhs)  const { return val < rhs.val;  }
};

int Foo::foo_counter = 0;

//Create a hash function for Foo in order to use Foo with unordered_map
namespace std {
   template<> struct hash<Foo> {
       std::size_t operator()(const Foo &f) const {
           return std::hash<int>{}(f.val);
       }
   };
}

int main()
{
    std::unordered_map<Foo, int> umap;  
    Foo foo0, foo1, foo2, foo3;
    int d;

    std::cout << "\numap.insert(std::pair<Foo, int>(foo0, d))\n";
    umap.insert(std::pair<Foo, int>(foo0, d));
    //equiv. to: umap.insert(std::make_pair(foo0, d));

    std::cout << "\numap.insert(std::move(std::pair<Foo, int>(foo1, d)))\n";
    umap.insert(std::move(std::pair<Foo, int>(foo1, d)));
    //equiv. to: umap.insert(std::make_pair(foo1, d));

    std::cout << "\nstd::pair<Foo, int> pair(foo2, d)\n";
    std::pair<Foo, int> pair(foo2, d);

    std::cout << "\numap.insert(pair)\n";
    umap.insert(pair);

    std::cout << "\numap.emplace(foo3, d)\n";
    umap.emplace(foo3, d);

    std::cout << "\numap.emplace(11, d)\n";
    umap.emplace(11, d);

    std::cout << "\numap.insert({12, d})\n";
    umap.insert({12, d});

    std::cout.flush();
}

আমি পেয়েছিলাম যে আউটপুট ছিল:

Foo() with val:                0
Foo() with val:                1
Foo() with val:                2
Foo() with val:                3

umap.insert(std::pair<Foo, int>(foo0, d))
Foo(Foo &) with val:           4    created from:       0
Foo(Foo&&) moving:             4    and changing it to: 5
~Foo() destroying:             4

umap.insert(std::move(std::pair<Foo, int>(foo1, d)))
Foo(Foo &) with val:           6    created from:       1
Foo(Foo&&) moving:             6    and changing it to: 7
~Foo() destroying:             6

std::pair<Foo, int> pair(foo2, d)
Foo(Foo &) with val:           8    created from:       2

umap.insert(pair)
Foo(const Foo &) with val:     9    created from:       8

umap.emplace(foo3, d)
Foo(Foo &) with val:           10   created from:       3

umap.emplace(11, d)
Foo(int) with val:             11

umap.insert({12, d})
Foo(int) with val:             12
Foo(const Foo &) with val:     13   created from:       12
~Foo() destroying:             12

~Foo() destroying:             8
~Foo() destroying:             3
~Foo() destroying:             2
~Foo() destroying:             1
~Foo() destroying:             0
~Foo() destroying:             13
~Foo() destroying:             11
~Foo() destroying:             5
~Foo() destroying:             10
~Foo() destroying:             7
~Foo() destroying:             9

লক্ষ্য করুন:

  1. একটি unordered_map সর্বদা অভ্যন্তরীণভাবে Foo অবজেক্টগুলি (এবং না বলে, Foo * গুলি) কী হিসাবে সংরক্ষণ করে, যা যখন unordered_map ধ্বংস হয় তখন সব ধ্বংস হয়। এখানে, unordered_map এর অভ্যন্তরীণ কীগুলি 13, 11, 5, 10, 7 এবং 9 টির মধ্যে ছিল।

    • তাই টেকনিক্যালি, আমাদের unordered_map প্রকৃতপক্ষে std::pair<const Foo, int> বস্তু সঞ্চয় করে, যা Foo বস্তুগুলি সংরক্ষণ করে। কিন্তু কিভাবে emplace() নীচের insert() হাইলাইট বক্স থেকে আলাদা emplace() এর "বড় ছবি ধারণা" বোঝার জন্য, অস্থায়ীভাবে এই std::pair object সম্পূর্ণরূপে প্যাসিভ হিসাবে কল্পনা করা ঠিক আছে। একবার আপনি এই "বড় ছবির ধারণাটি" বোঝেন, তখন এটি ব্যাক আপ করুন এবং বুঝবেন কিভাবে এই মধ্যস্থতাকারী std::pair object দ্বারা unordered_map সূক্ষ্ম, কিন্তু গুরুত্বপূর্ণ, প্রযুক্তিগতগুলি প্রবর্তন করে।
  2. foo0 , foo1 , এবং foo2 foo0 সন্নিবেশ foo0 Foo এর অনুলিপি / সরানো কন্সট্রাক্টরগুলির 2 এবং Foo এর ধ্বংসকারীকে 2 টি কল করতে হবে (যেমন আমি এখন বর্ণনা করছি):

    ক। foo0 এবং foo1 প্রতিটি সন্নিবেশ করা একটি অস্থায়ী বস্তু তৈরি করেছে (যথাক্রমে foo4 এবং foo6 ) যার ধ্বংসকারীকে অবিলম্বে সন্নিবেশ সমাপ্তির পরে ডাকা হয়েছিল। উপরন্তু, unordered_map এর অভ্যন্তরীণ Foo গুলি (যা foos 5 এবং 7 হয়) এছাড়াও unordered_map ধ্বংস করা হয় যখন তাদের ধ্বংসকারী বলা হয়েছে।

    খ। foo2 সন্নিবেশ করার জন্য, আমরা পরিবর্তে প্রথমে স্পষ্টভাবে একটি অ-অস্থায়ী জোড়া বস্তু ( pair বলা) তৈরি করেছি, যা foo2 এর অনুলিপি কন্সট্রাকটরকে foo2 তৈরি করে ( pair of অভ্যন্তরীণ সদস্য হিসাবে foo8 তৈরি করে)। আমরা তারপরে এই জুড়িটি insert() , যার ফলে unordered_map তার নিজস্ব অভ্যন্তরীণ কপি ( foo9 ) তৈরি করতে অনুলিপি কনস্ট্রাকটরকে পুনরায় ( foo8 foo9 ) কল করে। foo গুলি 0 এবং 1 এর মতো, শেষ ফলাফলটি এই সন্নিবেশের জন্য দুটি বিধ্বংসী কল ছিল যার মধ্যে কেবলমাত্র পার্থক্যটি ছিল যে foo8 এর ধ্বংসকারী শুধুমাত্র যখন insert() পরে বলা হয়ে যাওয়ার পরিবর্তে main() এর শেষে foo8 হয়েছিল তখনই বলা হয়েছিল ।

  3. Foo3 foo3 করা শুধুমাত্র 1 টি অনুলিপি / সরানো কন্সট্রাকটর কল (অভ্যন্তরীণভাবে unordered_mapfoo10 তৈরি) এবং Foo এর বিধ্বংসীর জন্য শুধুমাত্র 1 টি কল। (আমি পরে এই ফিরে পাবেন)।

  4. foo11 , আমরা সরাসরি পূর্ণসংখ্যা 11 এ emplace(11, d)foo11 করেছি যাতে unordered_map Foo(int) কন্সট্রকটারকে কল করবে যখন execution তার emplace() পদ্ধতির মধ্যে থাকবে। (2) এবং (3) এর থেকে ভিন্ন, আমাদের এটি করার জন্য কিছু প্রাক-বহিষ্কারকারী foo বস্তুও প্রয়োজন ছিল না। গুরুত্বপূর্ণভাবে, লক্ষ্য করুন যে একটি Foo কন্সট্রাকটর শুধুমাত্র 1 কল ঘটেছে।

  5. আমরা তারপর insert({12, d}) পূর্ণসংখ্যা 12 সরাসরি পাস। emplace(11, d) সাথে emplace(11, d) insert({12, d}) কলটি ফু এর কন্সট্রকটারে দুটি কল করেছে।

এই দেখায় insert() এবং emplace() মধ্যে প্রধান "বড় ছবি" পার্থক্যটি হল:

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

দ্রষ্টব্য: প্রায় প্রায় সবসময় প্রায় কারণ ব্যাখ্যা করা হয়)।

  1. অব্যাহত: umap.emplace(foo3, d) Foo এর অ-কন্স কপি কন্সট্রাকটর বলা কল করার কারণ হল: আমরা emplace() ব্যবহার করছি, তাই, কম্পাইলারটি জানেন যে foo3 (একটি অ- foo3 Foo বস্তু) হল কিছু Foo কনস্ট্রাক্টর একটি যুক্তি হতে বোঝানো। এই ক্ষেত্রে, সবচেয়ে উপযুক্ত Foo কন্সট্রাকটর অ-কন্স কপি গঠন Foo(Foo& f2) । এই কারণে umap.emplace(foo3, d) একটি কপি কন্সট্রাকটর বলা হয় যখন umap.emplace(11, d) না।

উপসংহার:

I. উল্লেখ্য যে insert() এর একটি ওভারলোড আসলে emplace() সমতুল্যএই cppreference.com পৃষ্ঠায় বর্ণিত হিসাবে, ওভারলোড template<class P> std::pair<iterator, bool> insert(P&& value) (যা এই cppreference.com পৃষ্ঠাতে insert() ) (ওভারলোড (2)) insert() emplace(std::forward<P>(value))

২। কোথা থেকে যেতে হবে?

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

std::cout << "\numap.insert({{" << Foo::foo_counter << ", d}})\n";
umap.insert({{Foo::foo_counter, d}});
//but umap.emplace({{Foo::foo_counter, d}}); results in a compile error!

std::cout << "\numap.insert(std::pair<const Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<const Foo, int>({Foo::foo_counter, d}));
//The above uses Foo(int) and then Foo(const Foo &), as expected. but the
// below call uses Foo(int) and the move constructor Foo(Foo&&). 
//Do you see why?
std::cout << "\numap.insert(std::pair<Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<Foo, int>({Foo::foo_counter, d}));
//Not only that, but even more interesting is how the call below uses all 
// three of Foo(int) and the Foo(Foo&&) move and Foo(const Foo &) copy 
// constructors, despite the below call's only difference from the call above 
// being the additional { }.
std::cout << "\numap.insert({std::pair<Foo, int>({" << Foo::foo_counter << ", d})})\n";
umap.insert({std::pair<Foo, int>({Foo::foo_counter, d})});


//Pay close attention to the subtle difference in the effects of the next 
// two calls.
int cur_foo_counter = Foo::foo_counter;
std::cout << "\numap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}}) where " 
  << "cur_foo_counter = " << cur_foo_counter << "\n";
umap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}});

std::cout << "\numap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}}) where "
  << "Foo::foo_counter = " << Foo::foo_counter << "\n";
umap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}});


//umap.insert(std::initializer_list<std::pair<Foo, int>>({{Foo::foo_counter, d}}));
//The call below works fine, but the commented out line above gives a 
// compiler error. It's instructive to find out why. The two calls
// differ by a "const".
std::cout << "\numap.insert(std::initializer_list<std::pair<const Foo, int>>({{" << Foo::foo_counter << ", d}}))\n";
umap.insert(std::initializer_list<std::pair<const Foo, int>>({{Foo::foo_counter, d}}));

আপনি শীঘ্রই দেখতে পাবেন যে std::pair কন্সট্রাকটর ( reference ) কোন ওভারলোড unordered_map দ্বারা ব্যবহৃত হচ্ছে তা কতগুলি বস্তু অনুলিপি, সরানো, তৈরি, এবং / অথবা ধ্বংস হয়ে যাওয়ার উপর গুরুত্বপূর্ণ প্রভাব ফেলতে পারে এবং যখন এটি ঘটে।

খ। std::unordered_multiset পরিবর্তে আপনি অন্য কোন কন্টেইনার ক্লাস (উদাহরণস্বরূপ std::set বা std::unordered_multiset ) ব্যবহার করলে কী হবে তা দেখুন।

গ। একটি unordered_map (যেমন unordered_map<Foo, int> এর পরিবর্তে unordered_map<Foo, Goo> ব্যবহার করুন) এর পরিবর্তে একটি int পরিবর্তে একটি Goo বস্তুর (কেবলমাত্র Foo এর একটি Foo অনুলিপি) ব্যবহার করুন এবং দেখুন কতগুলি এবং কোন Goo কন্সট্রাক্টর ডাকল. (স্পোলার: একটি প্রভাব আছে তবে এটি খুব নাটকীয় নয়।)





emplace