c++ tutorial সি++ 11 রেভেলিউস এবং স্যাম্যান্টিকস বিভ্রান্তি সরান(রিটার্ন স্টেটমেন্ট)




সি++ প্রোগ্রামিং বই (5)

আমি Rvalue রেফারেন্স বুঝতে এবং সি ++ 11 এর semantics সরানোর চেষ্টা করছি।

এই উদাহরণের মধ্যে পার্থক্য কি, এবং তাদের কোন ভেক্টর কপি করতে যাচ্ছে?

প্রথম উদাহরণ

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> &&rval_ref = return_vector();

দ্বিতীয় উদাহরণ

std::vector<int>&& return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

তৃতীয় উদাহরণ

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

যারা কোন অতিরিক্ত কপি করবেন না। এমনকি যদি আরভিও ব্যবহার করা না হয়, তবে নতুন মান বলছে যে আমি যখন বিশ্বাস করি তখন কপি নির্মাণের জন্য অনুলিপি নির্মাণ পছন্দ করা হয়।

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


প্রথম উদাহরণ

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> &&rval_ref = return_vector();

প্রথম উদাহরণ rval_ref দ্বারা ধরা হয় যা একটি অস্থায়ী ফিরে। যে অস্থায়ী তার জীবন rval_ref সংজ্ঞা অতিক্রম প্রসারিত হবে এবং আপনি এটি মান দ্বারা ধরা হয়েছে হিসাবে আপনি এটি ব্যবহার করতে পারেন। এই নিম্নলিখিত খুব অনুরূপ:

const std::vector<int>& rval_ref = return_vector();

যেহেতু আমার পুনঃলিখনে আপনি সম্ভবত একটি non-const পদ্ধতিতে rval_ref ব্যবহার করতে পারবেন না।

দ্বিতীয় উদাহরণ

std::vector<int>&& return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

দ্বিতীয় উদাহরণে আপনি একটি রান টাইম ত্রুটি তৈরি করেছেন। rval_ref এখন ফাংশনের ভেতর ধ্বংসপ্রাপ্ত rval_ref একটি রেফারেন্স ধারণ করে। কোন ভাগ্য সঙ্গে, এই কোড অবিলম্বে ক্র্যাশ হবে।

তৃতীয় উদাহরণ

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

আপনার তৃতীয় উদাহরণ প্রায় আপনার প্রথম সমতুল্য। std::move tmp std::move অপ্রয়োজনীয় এবং আসলে কার্যক্ষমতা হ্রাসকরণ হতে পারে কারণ এটি রিটার্ন মান অপ্টিমাইজেশানকে বাধা দেবে।

আপনি যা করছেন তা কোড করার সেরা উপায় হল:

ভাল অভ্যাস

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> rval_ref = return_vector();

যেমন আপনি সি ++ 03 তে ঠিক করবেন। tmp প্রত্যক্ষভাবে ফিরে বিবৃতি একটি rvalue হিসাবে গণ্য করা হয়। এটি রিটার্ন-মান-অপ্টিমাইজেশান (কোন অনুলিপি, কোন পদক্ষেপ নেই) এর মাধ্যমে ফেরত দেওয়া হবে, অথবা যদি কম্পাইলার সিদ্ধান্ত নেয় যে এটি RVO সম্পাদন করতে পারে না তবে এটি ভেক্টর এর পদক্ষেপ কন্সট্রাকটরটি ফিরতি করার জন্য ব্যবহার করবে । শুধুমাত্র যদি RVO সঞ্চালিত না হয়, এবং যদি ফেরত টাইপটিতে কোনও পদক্ষেপ কন্সট্রাকটর না থাকে তবে কপি কন্সট্রকটারটি প্রত্যাবর্তনের জন্য ব্যবহার করা হবে।


সাধারণ উত্তরটি আপনাকে রেভালিউ রেফারেন্সগুলির জন্য কোড লিখতে হবে যেমন আপনি নিয়মিত রেফারেন্স কোডটি ব্যবহার করবেন এবং আপনার সাথে মানসিক ভাবে 99% একই আচরণ করা উচিত। এই রেফারেন্স ফেরত সম্পর্কে পুরানো নিয়ম অন্তর্ভুক্ত করা হয় (অর্থাৎ স্থানীয় পরিবর্তনশীল একটি রেফারেন্স ফেরত না)।

আপনি যদি টেমপ্লেট কন্টেইনার ক্লাসটি লেখেন না যা std :: ফরওয়ার্ডের সুবিধা নিতে হবে এবং জেনভিক ফাংশন লিখতে সক্ষম হবেন যা লভ্যু বা রেভালিউ রেফারেন্সগুলি গ্রহণ করে তবে এটি কম বা সত্য।

সরানো কন্সট্রাকটর এবং পদক্ষেপ বরাদ্দের বড় সুবিধাগুলির মধ্যে একটি হল যে আপনি যদি তাদের সংজ্ঞায়িত করেন, তবে কম্পাইলার তাদের ক্ষেত্রে ব্যবহার করতে পারে RVO (রিটার্ন মান অপ্টিমাইজেশান) এবং NRVO (নাম দেওয়া রিটার্ন মান অপ্টিমাইজেশান) নাম্বার করতে ব্যর্থ হয়। এই পদ্ধতিগুলি থেকে দক্ষতার সাথে মান দ্বারা পাত্রে এবং স্ট্রিং মত ব্যয়বহুল বস্তু ফিরে জন্য বেশ বিশাল।

এখন যেখানে র্যাভেল রেফারেন্সগুলির সাথে বিষয়গুলি আকর্ষণীয় হয়, তখন আপনি স্বাভাবিক ফাংশনগুলিতে আর্গুমেন্ট হিসাবে তাদের ব্যবহার করতে পারেন। এটি আপনাকে কনস্টারগুলি লিখতে দেয় যা দুটি কনস্ট রেফারেন্সের জন্য ওভারলোড থাকে (কনফ foo & অন্যান্য) এবং rvalue রেফারেন্স (foo && অন্যান্য)। এমনকি যদি একটি নিছক কনস্ট্রাক্টর কল দিয়ে পাস করার পক্ষে যুক্তি খুব অলস হয় তবে তা এখনও করা যেতে পারে:

std::vector vec;
for(int x=0; x<10; ++x)
{
    // automatically uses rvalue reference constructor if available
    // because MyCheapType is an unamed temporary variable
    vec.push_back(MyCheapType(0.f));
}


std::vector vec;
for(int x=0; x<10; ++x)
{
    MyExpensiveType temp(1.0, 3.0);
    temp.initSomeOtherFields(malloc(5000));

    // old way, passed via const reference, expensive copy
    vec.push_back(temp);

    // new way, passed via rvalue reference, cheap move
    // just don't use temp again,  not difficult in a loop like this though . . .
    vec.push_back(std::move(temp));
}

STL কন্টেইনারগুলি প্রায়শই কিছু (হ্যাশ কী এবং মান, ভেক্টর সন্নিবেশ ইত্যাদি) জন্য ওভারলোডগুলি সরানোর জন্য আপডেট করা হয়েছে, এবং আপনি তাদের সবচেয়ে বেশি দেখতে পাবেন।

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

TextureHandle CreateTexture(int width, int height, ETextureFormat fmt, string&& friendlyName)
{
    std::unique_ptr<TextureObject> tex = D3DCreateTexture(width, height, fmt);
    tex->friendlyName = std::move(friendlyName);
    return tex;
}

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

// move from temporary
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string("Checkerboard"));

অথবা

// explicit move (not going to use the variable 'str' after the create call)
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, std::move(str));

অথবা

// explicitly make a copy and pass the temporary of the copy down
// since we need to use str again for some reason
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string(str));

কিন্তু এই কম্পাইল হবে না!

string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, str);

ইতিমধ্যে প্রথম উত্তর মন্তব্য উল্লেখ হিসাবে, return std::move(...); নির্মাণ স্থানীয় ভেরিয়েবল ফেরত ছাড়া অন্য ক্ষেত্রে একটি পার্থক্য করতে পারেন। এখানে একটি runnable উদাহরণ যা আপনি কোনও সদস্য বস্তুর সাথে এবং std::move() ছাড়া ফেরত দিলে কী হয় তা নথিভুক্ত করে:

#include <iostream>
#include <utility>

struct A {
  A() = default;
  A(const A&) { std::cout << "A copied\n"; }
  A(A&&) { std::cout << "A moved\n"; }
};

class B {
  A a;
 public:
  operator A() const & { std::cout << "B C-value: "; return a; }
  operator A() & { std::cout << "B L-value: "; return a; }
  operator A() && { std::cout << "B R-value: "; return a; }
};

class C {
  A a;
 public:
  operator A() const & { std::cout << "C C-value: "; return std::move(a); }
  operator A() & { std::cout << "C L-value: "; return std::move(a); }
  operator A() && { std::cout << "C R-value: "; return std::move(a); }
};

int main() {
  // Non-constant L-values
  B b;
  C c;
  A{b};    // B L-value: A copied
  A{c};    // C L-value: A moved

  // R-values
  A{B{}};  // B R-value: A copied
  A{C{}};  // C R-value: A moved

  // Constant L-values
  const B bc;
  const C cc;
  A{bc};   // B C-value: A copied
  A{cc};   // C C-value: A copied

  return 0;
}

সম্ভবত, return std::move(some_member); শুধুমাত্র আপনি যদি নির্দিষ্ট শ্রেণীর সদস্যকে সরাতে চান, তবে কেবলমাত্র অর্থাত্ বুঝে যায়, উদাহরণস্বরূপ, যেখানে class C স্বল্প-জীবিত অ্যাডাপ্টার বস্তুর প্রতিনিধিত্ব করে, এটি struct A দৃষ্টান্ত তৈরির একমাত্র উদ্দেশ্য সহ।

লক্ষ্য করুন যে struct A সর্বদা class B বাইরে কপি হয়ে যায়, এমনকি class B বস্তুটি একটি R- মানও থাকে। এই কারণেই কম্পাইলারের কোন স্ট্যাটাস class B এর উদাহরণ struct A এর কোনও উপায় নেই বলে আর ব্যবহার করা হবে না। class C , কম্পাইলারটিতে std::move() চলতি std::move() থেকে এই তথ্য থাকে, তাই struct A সরানো হয় , যতক্ষণ না class C উদাহরণ ধ্রুবক হয়।


প্রতি একটি উত্তর না, কিন্তু একটি গাইডলাইন। বেশিরভাগ সময়ে স্থানীয় T&& পরিবর্তনশীল ঘোষণা করার ক্ষেত্রে অনেক বেশি জ্ঞান নেই (যেমন আপনি std::vector<int>&& rval_ref ) করেছেন। foo(T&&) টাইপ পদ্ধতিতে ব্যবহার করতে এখনও আপনাকে std::move() করতে হবে। এমন একটি সমস্যা রয়েছে যা ইতিমধ্যে উল্লেখ করা হয়েছে যে যখন আপনি ফাংশন থেকে এমন rval_ref ফিরিয়ে দেওয়ার চেষ্টা করেন তখন আপনাকে স্ট্যান্ডার্ড রেফারেন্স-টু- rval_ref -অস্থায়ী-ফিয়াস্কো পাবেন।

বেশিরভাগ সময় আমি নিম্নলিখিত প্যাটার্ন সঙ্গে যেতে হবে:

// Declarations
A a(B&&, C&&);
B b();
C c();

auto ret = a(b(), c());

আপনি অস্থায়ী বস্তুগুলি ফিরিয়ে আনতে কোনও রেফারেন্স রাখেন না, সুতরাং আপনি (অনভিজ্ঞ) প্রোগ্রামারের ত্রুটি এড়াতে চান যা একটি সরানো বস্তুটি ব্যবহার করতে চায়।

auto bRet = b();
auto cRet = c();
auto aRet = a(std::move(b), std::move(c));

// Either these just fail (assert/exception), or you won't get 
// your expected results due to their clean state.
bRet.foo();
cRet.bar();

স্পষ্টতই সেখানে (যদিও বিরল) ঘটনা রয়েছে যেখানে একটি ফাংশন সত্যিই একটি T&& যা কোন অস্থায়ী বস্তুর একটি রেফারেন্স যা আপনি আপনার বস্তুর মধ্যে সরাতে পারেন।

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





c++-faq