সি ++ 11 টি মূল্যায়ন এবং পদার্থবিজ্ঞানের বিভ্রান্তি (রিটার্ন স্টেটমেন্ট)


435

আমি মূলসূত্রের রেফারেন্সগুলি বোঝার চেষ্টা করছি এবং সি ++ 11 এর শব্দার্থবিজ্ঞান স্থানান্তর করব।

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

প্রথম উদাহরণ

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();

50
স্থানীয় রেফারেন্সগুলি রেফারেন্স দ্বারা কখনও ফিরে আসবেন না। একটি মূল মূল্য উল্লেখ এখনও একটি রেফারেন্স।
ফ্রেডওভারফ্লো

63
উদাহরণগুলির মধ্যে শব্দার্থক পার্থক্যগুলি বোঝার জন্য এটি স্পষ্টতই ইচ্ছাকৃত ছিল
টারান্টুলা

@ ফ্রেড ওভারফ্লো ওল্ড প্রশ্ন, তবে আপনার মন্তব্য বুঝতে আমার একটু বেশি সময় লেগেছে। আমি মনে করি # 2 দিয়ে প্রশ্নটি std::move()একটি স্থির "অনুলিপি" তৈরি করেছিল কিনা ।
ডিভে

5
@ ডেভিডলাইভলি std::move(expression)কোনও কিছু তৈরি করে না, এটি কেবল এক্সপ্রেশনটির কাছে এক্সপ্রেশনটিকে কাস্ট করে। মূল্যায়নের প্রক্রিয়ায় কোনও বস্তু অনুলিপি করা বা সরানো হয় না std::move(expression)
ফ্রেডওভারফ্লো

উত্তর:


562

প্রথম উদাহরণ

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();

আমার পুনর্লিখন ব্যতীত আপনি স্পষ্টতই 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এখন tmpফাংশনের ভিতরে ধ্বংসাত্মক একটি রেফারেন্স ধারণ করে। যে কোনও ভাগ্যের সাথে, এই কোডটি তত্ক্ষণাত ক্রাশ হবে।

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

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();

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

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

ভাল অভ্যাস

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

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

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


64
আপনি যখন কোনও স্থানীয় বস্তুকে মান দ্বারা ফিরিয়ে আনেন তখন সংকলকরা আরভিও করবে এবং স্থানীয়র ধরণ এবং ফাংশনটির রিটার্ন একই রকম হবে এবং সিভি-কোয়ালিটিড (কনট টাইপগুলি রিটার্ন করবেন না)। শর্তটি (:?) বিবৃতি দিয়ে আরভিওকে বাধা দিতে পারে সেজন্য ফিরে যাওয়া থেকে দূরে থাকুন। লোকালটিকে অন্য কোনও ফাংশনে আবদ্ধ করবেন না যা লোকালটির কাছে কোনও রেফারেন্স দেয়। শুধু return my_local;। একাধিক রিটার্নের বিবৃতি ঠিক আছে এবং আরভিওকে বাধা দেবে না।
হাওয়ার্ড হিন্যান্ট

27
একটি সতর্কবাণী রয়েছে: কোনও স্থানীয় অবজেক্টের কোনও সদস্যকে ফিরিয়ে দেওয়ার সময়, সরানোটি অবশ্যই স্পষ্ট হওয়া উচিত।
বালক

5
@ ননসেন্সএটিএল: রিটার্ন লাইনে কোনও অস্থায়ী তৈরি করা হয়নি। moveএকটি অস্থায়ী তৈরি করে না। এটি কোনও জালিয়াতকে মূল্য দেয়, কোনও অনুলিপি তৈরি করে না, কিছুই তৈরি করে না, কিছুই ধ্বংস করে না। সেই উদাহরণটি হুবহু একই অবস্থা যেমন আপনি লভ্যালু-রেফারেন্স দিয়ে ফিরে এসেছেন এবং moveরিটার্ন লাইনটি সরিয়ে ফেলেছেন : যে কোনও উপায়ে আপনি ফাংশনের অভ্যন্তরে স্থানীয় ভেরিয়েবলের সাথে ঝাঁকুনির রেফারেন্স পেয়েছেন এবং যা ধ্বংস হয়েছে।
হাওয়ার্ড হিন্যান্ট

15
"একাধিক রিটার্নের স্টেটমেন্টগুলি ঠিক আছে এবং আরভিওকে বাধা দেবে না": যদি তারা একই পরিবর্তনশীলটি ফেরত দেয় তবেই ।
অনুচ্ছেদে

5
@ প্রতিলিপি: আপনি সঠিক বলেছেন। আমি যতটা ইচ্ছা করেছিলাম ঠিক তেমন কথা বলছিলাম না। আমি বোঝাতে চেয়েছিলাম যে একাধিক রিটার্ন স্টেটমেন্টগুলি আরভিও থেকে সংকলককে নিষিদ্ধ করে না (যদিও এটি বাস্তবায়ন করা অসম্ভব করে তোলে), এবং সুতরাং রিটার্ন এক্সপ্রেশনটি এখনও একটি মূল্য হিসাবে বিবেচিত হয়।
হাওয়ার্ড হিন্যান্ট

42

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

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

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

এখন ব্যতীত, ভেক্টর সরানো হয়েছে। ব্যবহারকারী একটি বর্গ ক্ষেত্রে বেশীরভাগ এটা এর rvalue রেফারেন্স সাথে মোকাবিলা করে না।


আপনি কি সত্যই নিশ্চিত যে তৃতীয় উদাহরণটি ভেক্টর অনুলিপি করতে চলেছে?
টারান্টুলা

@ টারান্টুলা: এটি আপনার ভেক্টরকে আবদ্ধ করবে। ভাঙ্গার আগে এটি করা বা না করে অনুলিপি করা আসলে কোনও বিষয় নয়।
কুকুরছানা

4
আপনার প্রস্তাবের জালিয়াতির কোনও কারণ আমি দেখতে পাচ্ছি না। স্থানীয় মূল্যায়নের রেফারেন্স ভেরিয়েবলকে কোনও মূল্যের সাথে বেঁধে রাখা পুরোপুরি ঠিক। সেক্ষেত্রে অস্থায়ী অবজেক্টের জীবনকাল মূল্যের রেফারেন্স ভেরিয়েবলের আজীবন প্রসারিত হয়।
ফ্রেডওভারফ্লো

1
আমি একটি স্পষ্টকরণের একটি বিষয়, যেহেতু আমি এটি শিখছি। এই নতুন উদাহরণে, ভেক্টর tmpনয় সরানো মধ্যে rval_ref, কিন্তু সরাসরি মধ্যে লিখিত rval_refRVO ব্যবহার (অর্থাত বিলোপ কপি)। std::moveস্বীকৃতি এবং অনুলিপি মধ্যে পার্থক্য আছে । একজন std::moveএখনও কিছু তথ্য কপি করা থাকতে পারে; কোনও ভেক্টরের ক্ষেত্রে, একটি নতুন ভেক্টর প্রকৃতপক্ষে অনুলিপি তৈরি করা হয় এবং তথ্য বরাদ্দ করা হয়, তবে ডেটা অ্যারের বেশিরভাগ অংশ কেবল পয়েন্টারটি অনুলিপি করে (মূলত) অনুলিপি করা হয়। অনুলিপি এলিজেন সমস্ত অনুলিপিগুলির 100% এড়িয়ে চলে।
লাকাটা

@ মারকলাকাটা এটি এনআরভিও, আরভিও নয়। সিআর ++ 17 এ এমনকি এনআরভিও .চ্ছিক। যদি এটি প্রয়োগ না করা হয় তবে মুদ্রা rval_refকনস্ট্রাক্টর ব্যবহার করে রিটার্ন মান এবং ভেরিয়েবল উভয়ই নির্মিত হয় std::vector। কোনও অনুলিপি নির্মাণকারীর সাথে / ছাড়া উভয়ই জড়িত std::movetmpএকটি হিসাবে গণ্য হবে rvalue মধ্যে returnএই ক্ষেত্রে বিবৃতি।
ড্যানিয়েল ল্যাঙ্গার

16

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

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

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

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

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));
}

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

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

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);

3

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

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

// 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&&যা একটি অস্থায়ী বস্তুর রেফারেন্স যা আপনি নিজের অবজেক্টে স্থানান্তর করতে পারেন।

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


2

এগুলির কেউ কোনও অতিরিক্ত অনুলিপি করবে না। আরভিও ব্যবহার না করা সত্ত্বেও, নতুন মানটি বলে যে আমি বিশ্বাস করি যে রিটার্নগুলি করার সময় নকল করা মুভ নির্মাণকেই প্রাধান্য দেওয়া হয়।

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


1

প্রথম উত্তরের মন্তব্যে যেমন ইতিমধ্যে উল্লেখ করা হয়েছে return std::move(...);, স্থানীয় ভেরিয়েবলগুলি ফেরত দেওয়া ব্যতীত অন্য কিছু ক্ষেত্রেও নির্মাণটি পার্থক্য আনতে পারে। এখানে একটি চলমান উদাহরণ যা আপনি যখন কোনও সদস্যের সাথে এবং ছাড়া কোনও জিনিস ফেরত দেন তখন কী হয় তা নথিগুলি 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বস্তুটি কোনও আর-মান হয়। কারণ এই সংকলকটির বলার উপায় নেই যে class Bএর দৃষ্টান্তটি struct Aআর ব্যবহার করা হবে না। ইন class C, কম্পাইলার থেকে এই তথ্য আছে std::move(), যা কেন struct Aপরার সরানো , যদি না দৃষ্টান্ত class Cধ্রুবক।

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