এটি কি সি ++ স্ট্যান্ডার্ড কমিটি দ্বারা উদ্দিষ্ট করা হয়েছে যে সি ++ ১১ টি আনর্ডারড_ম্যাপে এটি সন্নিবেশ করায় সেটি ধ্বংস করে দেয়?


114

আমি আমার জীবনের মাত্র তিন দিন হারিয়েছি খুব অদ্ভুত একটি বাগটি অনুসরণ করতে যেখানে আনর্ডার্ড_ম্যাপ :: সন্নিবেশ () আপনার সন্নিবেশ করা চলকটি ধ্বংস করে। এই অতি-স্পষ্ট আচরণটি কেবলমাত্র সাম্প্রতিক সংকলকগুলিতেই ঘটে: আমি দেখতে পেয়েছি যে ঝাঁকুনি ৩.২-৩.৪ এবং জিসিসি ৪.৮ এই "বৈশিষ্ট্য" প্রদর্শনের একমাত্র সংকলক।

এখানে আমার প্রধান কোড বেস থেকে কিছু হ্রাসকৃত কোড যা সমস্যাটি দেখায়:

#include <memory>
#include <unordered_map>
#include <iostream>

int main(void)
{
  std::unordered_map<int, std::shared_ptr<int>> map;
  auto a(std::make_pair(5, std::make_shared<int>(5)));
  std::cout << "a.second is " << a.second.get() << std::endl;
  map.insert(a); // Note we are NOT doing insert(std::move(a))
  std::cout << "a.second is now " << a.second.get() << std::endl;
  return 0;
}

আমি সম্ভবত বেশিরভাগ সি ++ প্রোগ্রামারদের মতো আউটপুটটিকেও এরকম কিছু দেখতে প্রত্যাশা করব:

a.second is 0x8c14048
a.second is now 0x8c14048

তবে ঝাঁকুনি দিয়ে 3.2-3.4 এবং জিসিসি 4.8 দিয়ে আমি এটি পরিবর্তে পেয়েছি:

a.second is 0xe03088
a.second is now 0

আপনি অর্ডারড_ম্যাপ :: সন্নিবেশ () এর জন্য দস্তাবেজগুলি http://www.cplsplus.com/references/unordered_map/unordered_map/insert/ এ পরীক্ষা না করা অবধি কোনও অর্থ হতে পারে যেখানে ওভারলোড নং 2 হবেন:

template <class P> pair<iterator,bool> insert ( P&& val );

যা লোভী সার্বজনীন রেফারেন্স মুভ ওভারলোড, অন্য কোনও ওভারলোডের সাথে কোনওরকম নয় এমন কিছু গ্রহণ করে এবং consum নির্মাণের সরাতে একটি VALUE_TYPE সেটিকে। তাহলে কেন আমাদের উপরের কোডগুলি এই ওভারলোডটি বেছে নিয়েছে, এবং আনর্ডার্ড_ম্যাপ :: মান_প্রকারের ওভারলোডকে সম্ভবত সম্ভবত প্রত্যাশা করবে না?

উত্তর মুখে তাকিয়ে: unordered_map :: VALUE_TYPE একজোড়া হল < const কোন int, এসটিডি :: shared_ptr> এবং কম্পাইলার সঠিকভাবে যে একজোড়া <মনে হবে int- এ , এসটিডি :: shared_ptr> পরিবর্তনীয় নয়। অতএব কম্পাইলার পদক্ষেপ সার্বজনীন রেফারেন্স জমিদার চয়ন করে এবং আসল নষ্ট করে সত্ত্বেও প্রোগ্রামার ব্যবহার করছেন না এসটিডি :: পদক্ষেপ () যা ইঙ্গিত আপনি যদি একটি পরিবর্তনশীল ধ্বংস পেয়ে সঙ্গে ঠিক আছ জন্য আদর্শ কনভেনশন হয়। সুতরাং সন্নিবেশ বিনষ্টকারী আচরণটি সি ++ 11 স্ট্যান্ডার্ড অনুযায়ী আসলে সঠিক এবং পুরানো সংকলকগুলি ছিল ভুল

আপনি সম্ভবত দেখতে পাবেন যে এই ত্রুটিটি সনাক্ত করতে আমি কেন তিন দিন সময় নিলাম। এটি কোনও বৃহত কোড বেসে একেবারেই স্পষ্ট ছিল না যেখানে আনর্ডারড_ম্যাপে টাইপটি টাইপডেফটি সোর্স কোডের পদে অনেক দূরে সংজ্ঞায়িত করা হয়েছিল, এবং টাইপডেফটি মান_প্রকারের মতো কিনা তা পরীক্ষা করার জন্য কারওর কাছে কখনও ঘটেনি।

সুতরাং স্ট্যাক ওভারফ্লোতে আমার প্রশ্নগুলি:

  1. পুরানো সংকলকরা কেন নতুন সংকলকগুলির মতো variোকানো ভেরিয়েবলগুলি ধ্বংস করে না? আমি বলতে চাইছি, এমনকি জিসিসি 4.7 এটিও করে না এবং এটি বেশ মানক।

  2. এই সমস্যাটি কি বহুল পরিচিত, কারণ অবশ্যই সংকলনকারীদের আপগ্রেড করা এমন কোডের কারণ হয়ে যাবে যা হঠাৎ কাজ বন্ধ করার জন্য কাজ করত?

  3. সি ++ স্ট্যান্ডার্ড কমিটি কি এই আচরণের পরিকল্পনা করেছিল?

  4. আপনি কীভাবে পরামর্শ দিবেন যে আনর্ডার্ড_ম্যাপ :: সন্নিবেশ () আরও ভাল আচরণের জন্য সংশোধন করা হবে? আমি এটি জিজ্ঞাসা করছি কারণ যদি এখানে সমর্থন থাকে তবে আমি এই আচরণটিকে ডাব্লুজি 21-র কাছে একটি নোট হিসাবে জমা দেওয়ার এবং তাদের আরও ভাল আচরণ বাস্তবায়নের অনুরোধ করছি।


10
শুধু কারণ এটি একটি সার্বজনীন সুত্র ব্যবহার মানে এই নয় সন্নিবেশিত মান সবসময় সরানোর সময় - এটা উচিত শুধুমাত্র কখনও rvalues জন্য তা করতে, যা প্লেইন aনয়। এটি একটি অনুলিপি করা উচিত। এছাড়াও, এই আচরণটি সম্পূর্ণরূপে stdlib উপর নির্ভর করে, সংকলক নয়।
Xeo

10
লাইব্রেরি বাস্তবায়নের ক্ষেত্রে এটি একটি বাগের মতো মনে হচ্ছে
ডেভিড রদ্রিগেজ - ড্রিবিস

4
"সুতরাং সন্নিবেশ বিনষ্টকারী আচরণটি সি ++ 11 স্ট্যান্ডার্ড অনুযায়ী আসলে সঠিক এবং পুরানো সংকলকগুলি ভুল ছিল।" দুঃখিত, কিন্তু আপনি ভুল। আপনি এই ধারণাটি C ++ স্ট্যান্ডার্ডের কোন অংশ থেকে পেয়েছেন? বিটিডাব্লু সিপিপ্লসপ্লাস ডটকম অফিসিয়াল নয়।
বেন ভয়েগট

1
আমি এটি আমার সিস্টেমে পুনরুত্পাদন করতে পারি না এবং আমি 4.9.0 20131223 (experimental)যথাক্রমে জিসিসি ৪.৮.২ ব্যবহার করছি । আউটপুট আমার জন্য a.second is now 0x2074088 (বা অনুরূপ)।

47
এটি ছিল জিসিসি বাগ 57619 , 4.8 সিরিজের একটি রিগ্রেশন যা 2013-06 এ 4.8.2 এর জন্য স্থির হয়েছিল।
কেসি

উত্তর:


83

অন্যরা মন্তব্যগুলিতে যেমন উল্লেখ করেছেন, "সর্বজনীন" নির্মাতা আসলে তার যুক্তি থেকে সর্বদা সরে যাওয়ার কথা নয়। যুক্তিটি সত্যই যদি কোনও মূল্যবোধ হয় তবে এটি সরিয়ে নেওয়ার কথা, এবং যদি এটি কোনও মূল্যবান হয় তবে অনুলিপি করুন।

আপনি যে আচরণটি লক্ষ্য করেন, যা সর্বদা চলতে থাকে, সেগুলি হল libstdc ++ এ একটি বাগ, যা এখন প্রশ্নের একটি মন্তব্য অনুসারে স্থির হয়েছে। এই কৌতূহলগুলির জন্য, আমি জি ++ - 4.8 শিরোনামে একবার দেখেছি।

bits/stl_map.h, লাইন 598-603

  template<typename _Pair, typename = typename
           std::enable_if<std::is_constructible<value_type,
                                                _Pair&&>::value>::type>
    std::pair<iterator, bool>
    insert(_Pair&& __x)
    { return _M_t._M_insert_unique(std::forward<_Pair>(__x)); }

bits/unordered_map.h, লাইনগুলি 365-370

  template<typename _Pair, typename = typename
           std::enable_if<std::is_constructible<value_type,
                                                _Pair&&>::value>::type>
    std::pair<iterator, bool>
    insert(_Pair&& __x)
    { return _M_h.insert(std::move(__x)); }

পরেরটি ভুলভাবে std::moveএটি ব্যবহার করা উচিত যেখানে এটি ব্যবহার করা উচিত std::forward


11
জঞ্জাল libstdc ++ ডিফল্ট হিসাবে জিসিসির stdlib ব্যবহার করে।
Xoo

আমি gcc 4.9 ব্যবহার করছি এবং আমি সন্ধান করছি libstdc++-v3/include/bits/। আমি একই জিনিস দেখতে পাচ্ছি না। আমি দেখছি { return _M_h.insert(std::forward<_Pair>(__x)); }। এটি ৪.৮ এর জন্য আলাদা হতে পারে তবে আমি এখনও চেক করি নি।

হ্যাঁ, তাই আমি অনুমান করি তারা বাগটি ঠিক করেছে।
ব্রায়ান

ব্রায়ান নোপ, আমি কেবলমাত্র আমার সিস্টেমের শিরোনামগুলি পরীক্ষা করেছি। একই জিনিস. 4.8.2 বিটিডাব্লু

খনি এর ৪.৮.১, সুতরাং আমি অনুমান করি এটি উভয়ের মধ্যে স্থির হয়েছিল।
ব্রায়ান

20
template <class P> pair<iterator,bool> insert ( P&& val );

যা একটি লোভী সার্বজনীন রেফারেন্স মুভ ওভারলোড, অন্য কোনও ওভারলোডের সাথে কোনওরূপ না মিলে এমন কিছু গ্রহণ করে এবং এটিকে মান_প্রকারে রূপান্তর করে।

এটিকে কিছু লোকজন সর্বজনীন রেফারেন্স বলছেন , তবে সত্যই এটি রেফারেন্স ভেঙে পড়ছে । আপনার ক্ষেত্রে, যেখানে আর্গুমেন্ট প্রকারের মূল্য রয়েছে তবে এটি যুক্তির মূল মূল্য হিসাবে pair<int,shared_ptr<int>>প্রমাণিত করবে না এবং এটি হওয়া উচিত নয় তা থেকে সরানো।

তাহলে কেন আমাদের উপরের কোডগুলি এই ওভারলোডটি বেছে নিয়েছে, এবং আনর্ডার্ড_ম্যাপ :: মান_প্রকারের ওভারলোডকে সম্ভবত সবচেয়ে বেশি প্রত্যাশা করবে না?

কারণ আপনি, আরও অনেক লোক আগে, ধারকটিতে ভুল ব্যাখ্যা করেছিলেন value_typevalue_typeএর *map(কিনা আদেশ বা unordered) হল pair<const K, T>আপনার ক্ষেত্রে যা, pair<const int, shared_ptr<int>>। না মিলার ধরণটি ওভারলোডটিকে সরিয়ে দেয় যা আপনি আশা করতে পারেন:

iterator       insert(const_iterator hint, const value_type& obj);

"সার্বজনীন রেফারেন্স", কেন এই নতুন লেমাটি বিদ্যমান তা আমি এখনও পাই না কারণ এটি নির্দিষ্ট কোনও নির্দিষ্ট অর্থ বোঝায় না বা এটি এমন কোনও জিনিসের জন্য ভাল নাম রাখে যা বাস্তবে পুরোপুরি "সর্বজনীন" নয়। টেমপ্লেটগুলি ভাষায় প্রবর্তিত হওয়ার সাথে সাথে সি ++ 11 স্ট্যান্ডার্ডের একটি নতুন স্বাক্ষর হিসাবে কিছু পুরানো ভাঙা নিয়মগুলি সি ++ মেটা-ভাষা আচরণের অংশ হিসাবে মনে রাখা ভাল। যাইহোক এই সার্বজনীন রেফারেন্স সম্পর্কে কথা বলার সুবিধা কী? ইতিমধ্যে এমন কিছু নাম এবং জিনিস রয়েছে যা যথেষ্ট অদ্ভুত, যেমন std::moveকিছুতেই স্থানান্তরিত হয় না।
ব্যবহারকারীর 2485710

2
@ user2485710 কখনও কখনও, অজ্ঞতা পরিতোষ হয় এবং সমস্ত রেফারেন্সের উপর ছাতা শব্দটি "সর্বজনীন রেফারেন্স" থাকায় আইএমএইচও বেশ অপ্রত্যাশিত হয়, এবং সেই টুইটটি std::forwardবাস্তব কাজটি করার জন্য ব্যবহার করার প্রয়োজন হয় ... স্কট মেয়ারস ফরওয়ার্ডিং (সার্বজনীন রেফারেন্স ব্যবহার) এর জন্য বেশ সোজা নিয়মগুলি সেট করে একটি ভাল কাজ করেছে।
মার্ক গার্সিয়া

1
আমার চিন্তায়, "সার্বজনীন রেফারেন্স" হ'ল ফাংশন টেম্পলেট প্যারামিটারগুলি ঘোষণার জন্য একটি প্যাটার্ন যা লভ্য এবং মান উভয়কে আবদ্ধ করতে পারে। "সার্বজনীন রেফারেন্স" পরিস্থিতি এবং অন্যান্য প্রসঙ্গে, সংজ্ঞাগুলিতে টেম্পলেট প্যারামিটারগুলি স্থাপন (অনুমিত বা নির্দিষ্ট) সংশোধন করার পরে "রেফারেন্স ভেঙে পড়া" যা ঘটে।
aschapler

2
@ এস্পেল্পার: সর্বজনীন রেফারেন্স হ'ল রেফারেন্সের একটি উপসেটের জন্য অভিনব নাম। আমি অজ্ঞতার সাথে একমত আনন্দিত এবং এও সত্য যে কোনও অভিনব নাম রাখা এটি সম্পর্কে কথা বলা সহজ এবং প্রবণতা করে এবং এটি আচরণের প্রচারে সহায়তা করে। এটি বলা হচ্ছে যে আমি নামের খুব বেশি অনুরাগী নই, কারণ এটি আসল নিয়মগুলিকে এমন এক কোণে ঠেলে দেয় যেখানে তাদের জানা দরকার না ... এটি হ'ল যতক্ষণ আপনি স্কট মায়ার্স বর্ণনা করেছেন তার বাইরে কোনও মামলা না করে।
ডেভিড রদ্রিগেজ - ফ্রিবি

অর্থহীন শব্দার্থবিজ্ঞান এখন, তবে যে পার্থক্যটি আমি সুপারিশ করার চেষ্টা করছিলাম: সর্বজনীন রেফারেন্সটি ঘটে যখন আমি কোনও ফাংশন প্যারামিটারকে ছাড়যোগ্য-টেম্পলেট-পরামিতি হিসাবে ঘোষণা করি &&; সংকলক যখন কোনও টেম্পলেট ইনস্ট্যান্ট করে তখন রেফারেন্স ভেঙে যায়। রেফারেন্স ভেঙে পড়ার কারণ সর্বজনীন রেফারেন্স কাজ করে, তবে আমার মস্তিষ্ক দুটি ডোমাকে একই ডোমেনে স্থাপন করা পছন্দ করে না।
aschepler
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.