আমরা কেন অনুলিপি করব না?


98

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

struct S
{
    S(std::string str) : data(std::move(str))
    {}
};

আমার প্রশ্নগুলি এখানে:

  • আমরা কেন কোনও মূল্য-রেফারেন্স নিচ্ছি না str?
  • একটি অনুলিপি ব্যয়বহুল হবে না, বিশেষত এরকম কিছু দেওয়া হয়েছে std::string?
  • লেখকের কোনও অনুলিপি তৈরির সিদ্ধান্ত নিয়ে সিদ্ধান্ত নেওয়ার কারণ কী হবে?
  • আমি নিজে এই কখন করা উচিত?

আমার কাছে মূর্খ ভুল বলে মনে হচ্ছে তবে আমি আগ্রহী যে বিষয়টি সম্পর্কে আরও জ্ঞানের সাথে কারও কিছু বলার আছে কিনা তা দেখতে আগ্রহী।
ডেভ


এই প্রশ্নোত্তর আমি প্রথমে লিঙ্ক করতে ভুলে গিয়েছিও এই বিষয়ের সাথে প্রাসঙ্গিক হতে পারে।
অ্যান্ডি প্রোল

উত্তর:


97

আমি আপনার প্রশ্নের উত্তর দেওয়ার আগে, আপনার একটি জিনিস ভুল হয়ে গেছে বলে মনে হচ্ছে: সি ++ 11 এ মূল্য নেওয়ার অর্থ সর্বদা অনুলিপি করা হয় না। যদি কোনও মূল্য পাস হয়ে যায়, তবে অনুলিপি করা না হয়ে সরানো হবে (একটি কার্যকর পদক্ষেপের নির্মাণকারী উপস্থিত থাকলে)। এবং std::stringএকটি সরানো কনস্ট্রাক্টর আছে।

সি ++ 03 এর বিপরীতে, সি ++ 11 এ প্রায়শই মূল্যের সাথে পরামিতিগুলি গ্রহণ করা বুদ্ধিমানের কারণ হিসাবে আমি নীচে ব্যাখ্যা করতে যাচ্ছি। প্যারামিটারগুলি কীভাবে গ্রহণ করবেন সে সম্পর্কে আরও সাধারণ নির্দেশিকাগুলির জন্য স্ট্যাকওভারফ্লোতে এই প্রশ্নোত্তরও দেখুন ।

আমরা কেন কোনও মূল্য-রেফারেন্স নিচ্ছি না str?

কারণ এর ফলে লভ্যগুলি পাস করা অসম্ভব হয়ে উঠবে যেমন:

std::string s = "Hello";
S obj(s); // s is an lvalue, this won't compile!

যদি Sকেবল কোনও নির্মাণকারী থাকে যা মূল্যগুলি গ্রহণ করে তবে উপরেরটির সংকলন হবে না।

একটি অনুলিপি ব্যয়বহুল হবে না, বিশেষত এরকম কিছু দেওয়া হয়েছে std::string?

আপনি একটি rvalue পাস, যে হবে সরানো মধ্যে str, এবং যে অবশেষে মধ্যে সরানো হবে data। কোনও অনুলিপি করা হবে না। অন্যদিকে যদি আপনি একটি মূল্যবান পাস করেন তবে সেই মানটি অনুলিপি করা হবে strএবং তারপরে moved ুকে যাবে data

সুতরাং এটি সংক্ষেপে, মূল্যগুলির জন্য দুটি পদক্ষেপ, একটি অনুলিপি এবং লভ্যালুগুলির জন্য একটি পদক্ষেপ।

লেখকের কোনও অনুলিপি তৈরির সিদ্ধান্ত নিয়ে সিদ্ধান্ত নেওয়ার কারণ কী হবে?

প্রথমত, যেমন আমি উপরে উল্লেখ করেছি, প্রথমটি সর্বদা অনুলিপি হয় না; এবং এটি বলেছে, উত্তরটি হ'ল: " কারণ এটি দক্ষ ( std::stringবস্তুর চলা সস্তা) এবং সহজ "।

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

এর অর্থ হ'ল মূল্য দিয়ে নেওয়া সমান মূল্য যেমন constলভ্যালুগুলি সরবরাহ করা হয় তার চেয়ে ভাল, এবং যখন মূল্যগুলি সরবরাহ করা হয় তখন আরও ভাল।

পিএস: কিছু প্রসঙ্গ সরবরাহ করার জন্য, আমি বিশ্বাস করি এটিই হল প্রশ্ন ও উত্তর যা ওপি উল্লেখ করছে।


4
এটি একটি সি ++ ১১ প্যাটার্ন উল্লেখ করা দরকার যা const T&আর্গুমেন্ট পাসের পরিবর্তে প্রতিস্থাপন করে: সবচেয়ে খারাপ ক্ষেত্রে (মূল্য) এটি একই তবে অস্থায়ী ক্ষেত্রে আপনাকে কেবল অস্থায়ী স্থানান্তর করতে হবে। উইন-উইন
সিয়াম

4
@ ইউজার ২০৩০6777: আপনি কোনও রেফারেন্স সঞ্চয় না করা অবধি সেই অনুলিপিটি আর পাওয়া যাবে না।
বেনিয়ামিন লিন্ডলি

5
@ ব্যবহারকারী 2030677: কপিটি আপনার যতক্ষণ প্রয়োজন প্রয়োজন ততক্ষণ ব্যয়বহুল তা কে যত্নশীল করে (এবং আপনি যদি নিজের সদস্যের অনুলিপি রাখতে চান তবে data)? আপনার কাছে অনুলিপি থাকলেও আপনার কাছে একটি অনুলিপি থাকবেconst
অ্যান্ডি প্রোল

4
@ বেনজামিন লিন্ডলি: প্রাথমিক হিসাবে আমি লিখেছিলাম: " এই অনুকরণের যে পদক্ষেপগুলি সস্তা, এই নকশার সামগ্রিক দক্ষতা বিবেচনা করার সময় তাদের ব্যবহারিকভাবে উপেক্ষা করা যেতে পারে। " সুতরাং হ্যাঁ, একটি পদক্ষেপের ওভারহেড থাকবে, তবে এটিকে উপেক্ষিত হিসাবে বিবেচনা করা উচিত যদি না প্রমাণ না পাওয়া যায় যে এটি একটি আসল উদ্বেগ যা একটি সাধারণ নকশাটিকে আরও দক্ষ কিছুতে রূপান্তরিত করার ন্যায্যতা প্রমাণ করে।
অ্যান্ডি প্রোল

4
@ ব্যবহারকারী 2030677: তবে এটি সম্পূর্ণ ভিন্ন উদাহরণ। আপনার প্রশ্ন থেকে উদাহরণস্বরূপ আপনি সর্বদা একটি অনুলিপি রাখা data!
অ্যান্ডি প্রোল

51

এটি কেন ভাল প্যাটার্ন তা বোঝার জন্য আমাদের সি ++ 03 এবং সি ++ 11 এ দুটি বিকল্পই পরীক্ষা করা উচিত।

আমাদের কাছে C ++ 03 পদ্ধতি রয়েছে std::string const&:

struct S
{
  std::string data; 
  S(std::string const& str) : data(str)
  {}
};

এই ক্ষেত্রে, সর্বদা একটি একক অনুলিপি সম্পাদিত হবে। যদি আপনি কোনও কাঁচা সি স্ট্রিং থেকে নির্মাণ করেন তবে একটি নির্মিত std::stringহবে, তারপরে আবার অনুলিপি করুন: দুটি বরাদ্দ।

এখানে একটি রেফারেন্স নেওয়ার সি ++ 03 পদ্ধতি রয়েছে std::string, তারপরে এটিকে লোকালটিতে অদলবদল করুন std::string:

struct S
{
  std::string data; 
  S(std::string& str)
  {
    std::swap(data, str);
  }
};

এটি "মুভ সেমেন্টিকস" এর C ++ 03 সংস্করণ, এবং swapপ্রায়শই এটি করার জন্য খুব সস্তা হতে অনুকূলিত করা যেতে পারে (অনেকটা এর মতো move)। এটি প্রসঙ্গে বিশ্লেষণ করা উচিত:

S tmp("foo"); // illegal
std::string s("foo");
S tmp2(s); // legal

এবং আপনাকে অস্থায়ী হিসাবে গঠনের জন্য জোর করে std::string, তারপরে এটি বাতিল করুন। (একটি অস্থায়ী std::stringঅ-কনস্ট্যান্ট রেফারেন্সকে আবদ্ধ করতে পারে না)। তবে শুধুমাত্র একটি বরাদ্দ করা হয়। সি ++ 11 সংস্করণটি গ্রহণ করবে &&এবং আপনাকে এটির সাথে std::moveবা একটি অস্থায়ী সহ কল ​​করতে হবে : এর জন্য কলারটি স্পষ্টভাবে কলের বাইরে একটি অনুলিপি তৈরি করে এবং সেই অনুলিপিটি ফাংশন বা কনস্ট্রাক্টারে স্থানান্তরিত করে।

struct S
{
  std::string data; 
  S(std::string&& str): data(std::move(str))
  {}
};

ব্যবহার:

S tmp("foo"); // legal
std::string s("foo");
S tmp2(std::move(s)); // legal

এরপরে, আমরা সম্পূর্ণ সি ++ 11 সংস্করণটি করতে পারি, যা অনুলিপি এবং উভয়কেই সমর্থন করে move:

struct S
{
  std::string data; 
  S(std::string const& str) : data(str) {} // lvalue const, copy
  S(std::string && str) : data(std::move(str)) {} // rvalue, move
};

এরপরে আমরা এটি কীভাবে ব্যবহৃত হয় তা পরীক্ষা করতে পারি:

S tmp( "foo" ); // a temporary `std::string` is created, then moved into tmp.data

std::string bar("bar"); // bar is created
S tmp2( bar ); // bar is copied into tmp.data

std::string bar2("bar2"); // bar2 is created
S tmp3( std::move(bar2) ); // bar2 is moved into tmp.data

এটি বেশ পরিষ্কার যে এই 2 ওভারলোড কৌশলটি উপরের দুটি সি ++ 03 শৈলীর চেয়ে কম না হলেও কম দক্ষ efficient আমি এই 2-ওভারলোড সংস্করণটিকে "সর্বাধিক অনুকূল" সংস্করণটি ডাব করব।

এখন, আমরা কপিরাইট-এর অনুলিপি পরীক্ষা করব:

struct S2 {
  std::string data;
  S2( std::string arg ):data(std::move(x)) {}
};

এই পরিস্থিতিতে প্রতিটি পরিস্থিতিতে:

S2 tmp( "foo" ); // a temporary `std::string` is created, moved into arg, then moved into S2::data

std::string bar("bar"); // bar is created
S2 tmp2( bar ); // bar is copied into arg, then moved into S2::data

std::string bar2("bar2"); // bar2 is created
S2 tmp3( std::move(bar2) ); // bar2 is moved into arg, then moved into S2::data

আপনি যদি এই সর্বাধিক অনুকূল "সংস্করণ" এর সাথে তুলনা করেন, আমরা ঠিক আরও একটি অতিরিক্ত করবো move! একবারে আমরা অতিরিক্ত কিছু করি না copy

সুতরাং আমরা যদি ধরে নিই যে moveএটি সস্তা, এই সংস্করণটি আমাদের সর্বাধিক অনুকূল সংস্করণ হিসাবে প্রায় একই পারফরম্যান্স পেয়েছে, তবে 2 গুণ কম কোড।

এবং যদি আপনি 2 থেকে 10 টি আর্গুমেন্ট বলছেন, কোড হ্রাস হ'ল তাত্পর্যপূর্ণ - 1 টি যুক্তির সাথে 2x গুণ কম, 4 টি সাথে 2x, 3x দিয়ে 8x, 4 সাথে 10xx, 10 টি যুক্তি দিয়ে।

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

এবং প্রচুর উত্পন্ন ফাংশনগুলির অর্থ বৃহত্তর এক্সিকিউটেবল কোড আকার, যা নিজেই কর্মক্ষমতা হ্রাস করতে পারে।

কয়েকটি moveএসের ব্যয়ের জন্য আমরা সংক্ষিপ্ত কোড এবং প্রায় একই কার্যকারিতা পাই এবং প্রায়শই কোড বোঝা সহজ হয়।

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

'বাই বাই ভ্যালু' কৌশলটির আরেকটি সুবিধা হ'ল প্রায়শই কনস্ট্রাক্টর মুভ কন্টাক্টর্টস That এর অর্থ হল যে ক্রিয়াকলাপগুলি যা মানকে গ্রহণ করে এবং তাদের যুক্তি থেকে সরে যায় প্রায়শই অযোগ্য হতে পারে, কোনও throwশরীরকে তার দেহ এবং কলিং স্কোপে নিয়ে যায় often (যারা কখনও কখনও এটি সরাসরি নির্মাণের মাধ্যমে এড়াতে পারে, বা আইটেমগুলি তৈরি করতে এবং moveযুক্তিতে তৈরি করতে পারে যেখানে নিক্ষেপ ঘটে সেখানে নিয়ন্ত্রণ করতে পারে methods) পদ্ধতিগুলিকে নোহ্রো তৈরি করা প্রায়শই উপযুক্ত।


আমি আরও যুক্ত করব যদি আমরা জানি যে আমরা একটি অনুলিপি তৈরি করব, আমাদের কম্পাইলারটি এটি করা উচিত, কারণ সংকলক সর্বদা আরও ভাল জানেন knows
রায়নিয়ারি

6
যেহেতু আমি এটি লিখেছি, আরেকটি সুবিধা আমার দিকে লক্ষ্য করা হয়েছিল: প্রায়শই অনুলিপি নির্মাণকারীরা নিক্ষেপ করতে পারে, অন্যদিকে মুভ কনস্ট্রাক্টর প্রায়শই থাকে noexcept। অনুলিপি ডেটা গ্রহণ করে, আপনি আপনার ফাংশন তৈরি করতে পারেন noexceptএবং কোনও অনুলিপি নির্মাণের কারণে আপনার ফাংশন অনুরোধের বাইরে সম্ভাব্য ছোঁড়া (যেমন মেমরির বাইরে ) ঘটতে পারে ।
ইয়াক্ক - অ্যাডাম নেভ্রামামন্ট

3 ওভারলোড কৌশলটিতে আপনার "লেভেলু নন-কনস্ট, কপি" সংস্করণটি কেন দরকার? "ল্যাভেলু কনস্ট, কপি" নন কনস্টের কেসটিও পরিচালনা করে না?
ব্রুনো মার্টিনেজ

@ ব্রুনোমার্টিনিজ আমরা না!
ইয়াক্ক - অ্যাডাম নেভ্রামামন্ট

13

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


অনুলিপি এবং অনুলিপি সমান্তরাল জন্য +1। আসলে এটির অনেক মিল রয়েছে imila
সিয়াম

11

আপনি সরানোর জন্য কনস্ট্রাক্টর এবং অনুলিপিটির জন্য একটি লিখে নিজেকে পুনরাবৃত্তি করতে চান না:

S(std::string&& str) : data(std::move(str)) {}
S(const std::string& str) : data(str) {}

এটি অনেকটা বয়লারপ্লেট কোড, বিশেষত যদি আপনার একাধিক যুক্তি থাকে। আপনার সমাধানটি একটি অনাবশ্যক পদক্ষেপের ব্যয়ের জন্য সেই অনুলিপিটিকে এড়িয়ে চলে। (তবে মুভ অপারেশনটি বেশ সস্তা হওয়া উচিত))

প্রতিযোগিতামূলক প্রতিমাটি হ'ল নিখুঁত ফরওয়ার্ডিং ব্যবহার করা:

template <typename T>
S(T&& str) : data(std::forward<T>(str)) {}

আপনি যে পরামিতিটি পাস করেছেন তার উপর নির্ভর করে টেমপ্লেট যাদুটি স্থানান্তর বা অনুলিপি বেছে নেবে It এটি মূলত প্রথম সংস্করণে প্রসারিত হয়, যেখানে উভয় কনস্ট্রাক্টর হাতে লেখা ছিল। পটভূমির তথ্যের জন্য, সর্বজনীন রেফারেন্সে স্কট মায়ারের পোস্ট দেখুন ।

একটি পারফরম্যান্স দিক থেকে, নিখুঁত ফরোয়ার্ডিং সংস্করণটি আপনার সংস্করণ থেকে উচ্চতর কারণ এটি অপ্রয়োজনীয় পদক্ষেপগুলি এড়িয়ে চলে। তবে, কেউ যুক্তি দিতে পারে যে আপনার সংস্করণটি পড়া এবং লেখা সহজ। সম্ভাব্য পারফরম্যান্সের প্রভাবটি বেশিরভাগ পরিস্থিতিতে, যাইহোক, তাত্পর্যপূর্ণ হওয়া উচিত নয়, সুতরাং এটি শেষ পর্যন্ত শৈলীর বিষয় বলে মনে হয়।

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