অস্থায়ী কোনও রেফারেন্সের ভিত্তিতে এই পরিসরের জন্য কে দোষী?


15

নিম্নলিখিত কোডটি প্রথম দৃষ্টিতে বরং নির্দোষ বলে মনে হচ্ছে। bar()কোনও গ্রন্থাগুলি কিছু লাইব্রেরির কার্যকারিতার সাথে ইন্টারঅ্যাক্ট করতে ফাংশনটি ব্যবহার করে । (এটি একটি bar()অস্থায়ী মান বা অনুরূপ কোনও রেফারেন্স ফেরত দেওয়ার পরেও দীর্ঘকাল ধরে কাজ করেছে )) তবে এখন এটি কেবল একটি নতুন উদাহরণ ফিরে আসছে BBআবার একটি ফাংশন রয়েছে a()যা পুনরাবৃত্তযোগ্য টাইপের কোনও অবজেক্টের রেফারেন্স দেয় A। ব্যবহারকারী এই অবজেক্টটি জিজ্ঞাসা করতে চায় যা Bদ্বারা পুনরায় ফিরে আসা অস্থায়ী বস্তুটি bar()পুনরাবৃত্তি শুরুর আগেই ধ্বংস হয়ে যাওয়ার কারণে সেগফল্টের দিকে নিয়ে যায়।

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

(একটি সম্পর্কিত প্রশ্ন হতে পারে: কেউ যদি সাধারণ নিয়মটি প্রতিষ্ঠা করে যে লুপ হেডারে একাধিক চেইন কল দ্বারা পুনরুদ্ধার করা এমন কোনও কিছুর উপরে কোডটি "রেঞ্জ-ভিত্তিক-ফর-পুনরাবৃত্তি") করা উচিত নয় কারণ এই কলগুলির মধ্যে কোনও কল ফিরে আসতে পারে rvalue?)

#include <algorithm>
#include <iostream>

// "Library code"
struct A
{
    A():
        v{0,1,2}
    {
        std::cout << "A()" << std::endl;
    }

    ~A()
    {
        std::cout << "~A()" << std::endl;
    }

    int * begin()
    {
        return &v[0];
    }

    int * end()
    {
        return &v[3];
    }

    int v[3];
};

struct B
{
    A m_a;

    A & a()
    {
        return m_a;
    }
};

B bar()
{
    return B();
}

// User code
int main()
{
    for( auto i : bar().a() )
    {
        std::cout << i << std::endl;
    }
}

6
কাকে দোষ দেবেন তা যখন আপনি বুঝতে পারলেন, পরবর্তী পদক্ষেপটি কী হবে? তাকে / তার উপর চিত্কার?
জেনসজি

7
না, আমি কেন করব? ভবিষ্যতে এই সমস্যাটি এড়াতে এই "প্রোগ্রাম" বিকাশের চিন্তা প্রক্রিয়াটি কোথায় ব্যর্থ হয়েছিল তা জানতে আমি আরও আগ্রহী।
hllnll

এটির মূল্যগুলির সাথে বা লুপগুলির জন্য পরিসীমা-ভিত্তিক কিছুই করার নেই, তবে ব্যবহারকারীর সাথে জীবনকাল সঠিকভাবে বোঝা যাচ্ছে না।
জেমস

সাইট-মন্তব্য: এটি সিডাব্লুজি 900 যা একটি ত্রুটি নয় হিসাবে বন্ধ ছিল। কয়েক মিনিট কিছু আলোচনা হতে পারে।
ডায়প

8
এর জন্য দোষী কে? প্রথম এবং সর্বাগ্রে বজর্ন স্ট্রস্ট্রপ এবং ডেনিস রিচি।
ম্যাসন হুইলারের

উত্তর:


14

আমি মনে করি যে মৌলিক সমস্যাটি সি ++ এর ভাষার বৈশিষ্ট্যগুলির (বা এর অভাব) এর সংমিশ্রণ। লাইব্রেরি কোড এবং ক্লায়েন্ট কোড উভয়ই যুক্তিসঙ্গত (সমস্যাটি যে সুস্পষ্ট থেকে দূরে রয়েছে তার প্রমাণ হিসাবে)। যদি অস্থায়ী জীবনকাল যথাযথভাবে Bবাড়ানো হয় (লুপের শেষে) কোনও সমস্যা হবে না।

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

প্রোগ্রামারকে স্পষ্টভাবে bar()একটি স্থানীয় ভেরিয়েবলে উন্নত করতে বাধ্য করা, এই জাতীয় বাজে কথা সনাক্ত করা এবং নিষেধ করা আরও বাঞ্ছনীয় । এটি সি ++ 11 এ সম্ভব নয় এবং সম্ভবত কখনও সম্ভব হবে না কারণ এর জন্য টীকাগুলির প্রয়োজন requires মরিচা এটি করে, যেখানে এর স্বাক্ষর .a()হবে:

fn a<'x>(bar: &'x B) -> &'x A { bar.a }
// If we make it as explicit as possible, or
fn a(&self) -> &A { self.a }
// if we make it a method and rely on lifetime elision.

এখানে 'xএকটি আজীবন পরিবর্তনশীল বা অঞ্চল, যা কোনও সংস্থান প্রাপ্তির সময়কালের জন্য একটি প্রতীকী নাম। সত্য কথা বলতে গেলে, জীবনকালগুলি ব্যাখ্যা করা শক্ত - বা আমরা এখনও সেরা ব্যাখ্যাটি বের করতে পারি নি - তাই আমি এই উদাহরণের জন্য নিজেকে সর্বনিম্ন প্রয়োজনীয়তার মধ্যে সীমাবদ্ধ করব এবং ঝুঁকির পাঠককে অফিসিয়াল ডকুমেন্টেশনে উল্লেখ করব

Orrowণ চেককারী লক্ষ্য করবে যে bar().a()লুপটি যতক্ষণ চালায় ততক্ষণ বেঁচে থাকার প্রয়োজন। জীবদ্দশায় একটি বাধ্যতা যেমন phrased 'x, আমরা লিখুন: 'loop <= 'x। এটি আরও লক্ষ করবে যে পদ্ধতিটির কল গ্রহণকারী bar(), একটি অস্থায়ী। দুটি পয়েন্টার একই জীবনকালের সাথে জড়িত, তাই 'x <= 'tempঅন্য বাধা।

এই দুটি বাধা পরস্পরবিরোধী! আমাদের দরকার 'loop <= 'x <= 'tempতবে 'temp <= 'loop, যা সমস্যাটিকে বেশ স্পষ্টভাবে ধরা দেয়। দ্বন্দ্বযুক্ত প্রয়োজনীয়তার কারণে, বগী কোডটি প্রত্যাখ্যান করা হয়। নোট করুন যে এটি একটি সংকলন-সময় চেক এবং মরিচা কোড সাধারণত সমতুল্য সি ++ কোড হিসাবে একই মেশিন কোডে ফলাফল দেয়, সুতরাং আপনার এটির জন্য রান-টাইম ব্যয় প্রয়োজন হবে না।

তবুও এটি কোনও ভাষায় যুক্ত করার জন্য একটি বড় বৈশিষ্ট্য এবং এটি সমস্ত কোড ব্যবহার করে তবেই কাজ করে। এপিআইয়ের নকশাটিও প্রভাবিত হয় (কিছু ডিজাইন যা সি ++ তে খুব বিপজ্জনক হবে তা ব্যবহারিক হয়ে ওঠে, অন্যদের জীবনকাল সহ সুন্দর খেলতে তৈরি করা যায় না)। হায়, এর অর্থ এটি সি ++ (বা কোনও ভাষা সত্যিই) প্রত্যাবর্তনমূলকভাবে যুক্ত করা ব্যবহারিক নয়। সংক্ষেপে, দোষটি জড়তা সফল ভাষাগুলির এবং এর মধ্যে যে বার্জনে ১৯৮৩ সালে বিগত ৩০ বছরের গবেষণা এবং সি ++ অভিজ্ঞতার পাঠগুলি অন্তর্ভুক্ত করার জন্য স্ফটিক বল এবং দূরদৃষ্টি ছিল না ;-)

অবশ্যই, এটি ভবিষ্যতে সমস্যা এড়াতে মোটেই সহায়ক নয় (যদি আপনি মরচে স্যুইচ না করেন এবং আর কখনও সি ++ ব্যবহার না করেন)। একাধিক শৃঙ্খলিত পদ্ধতি কল (যেটি বেশ সীমাবদ্ধ, এবং দূরবর্তীভাবে সমস্ত আজীবন সমস্যাগুলিও সমাধান করে না) দিয়ে দীর্ঘতর অভিব্যক্তি এড়াতে পারে। অথবা এক কম্পাইলার সহায়তা ছাড়া আরো একটি শৃঙ্খলাবদ্ধ মালিকানা নীতি অবলম্বন চেষ্টা করে দেখতে পারেন: দস্তাবেজ পরিষ্কারভাবে যে barমান এবং যে ফলাফল দেখায় B::a()আবশ্যক বাঁচিয়া থাকা না Bযার উপর a()প্রার্থনা করা হয়। দীর্ঘস্থায়ী রেফারেন্সের পরিবর্তে কোনও ফাংশনকে মান অনুসারে পরিবর্তন করার সময়, সচেতন হন যে এটি চুক্তির পরিবর্তন । তবু ত্রুটি প্রবণ, তবে কখন তা ঘটবে তা সনাক্ত করার প্রক্রিয়াটি দ্রুততর করতে পারে।


14

আমরা কি সি ++ বৈশিষ্ট্য ব্যবহার করে এই সমস্যাটি সমাধান করতে পারি?

সি ++ 11 সদস্য ফাংশন রেফ-কোয়ালিফায়ার যুক্ত করেছে, যা শ্রেণীর উদাহরণের মান বিভাগ (অভিব্যক্তি) সীমাবদ্ধ করে মেম্বার ফাংশনটি ডাকা যেতে পারে। উদাহরণ স্বরূপ:

struct foo {
    void bar() & {} // lvalue-ref-qualified
};

foo& lvalue ();
foo  prvalue();

lvalue ().bar(); // OK
prvalue().bar(); // error

beginসদস্য ফাংশনটি কল করার সময় , আমরা জানি যে সম্ভবত আমাদেরও endসদস্য ফাংশনটি কল করতে হবে (বা sizeপরিসীমাটির আকার পেতে এমন কিছু )। এটি প্রয়োজন যে আমরা একটি মূল্যবান কাজ করি, যেহেতু আমাদের এটির দু'বার সম্বোধন করা দরকার। অতএব আপনি যুক্তি দিতে পারেন যে এই সদস্যের কার্যগুলি লভ্যালু-রেফ-কোয়ালিফাইড হওয়া উচিত।

তবে এটি অন্তর্নিহিত সমস্যাটি সমাধান করতে পারে না: এলিয়াসিং। beginএবং endসদস্য ফাংশন ওরফে বস্তু, বা রিসোর্সের বস্তু দ্বারা পরিচালিত। যদি আমরা প্রতিস্থাপন করি beginএবং endএকটি একক ফাংশন দ্বারা range, আমাদের উচিত এমন একটি সরবরাহ করা উচিত যা মূল্যগুলিতে ডাকা যেতে পারে:

struct foo {
    vector<int> arr;

    auto range() & // C++14 return type deduction for brevity
    { return std::make_pair(arr.begin(), arr.end()); }
};

for(auto const& e : foo().range()) // error

এটি একটি বৈধ ব্যবহারের ক্ষেত্রে হতে পারে, তবে উপরোক্ত সংজ্ঞাটি এটি rangeঅস্বীকার করে। যেহেতু আমরা সদস্য ফাংশন কলের পরে অস্থায়ীটিকে সম্বোধন করতে পারি না, তাই ধারকটি ফেরত দেওয়া আরও যুক্তিসঙ্গত হতে পারে, অর্থাত্ একটি নিজস্ব পরিসর:

struct foo {
    vector<int> arr;

    auto range() &
    { return std::make_pair(arr.begin(), arr.end()); }

    auto range() &&
    { return std::move(arr); }
};

for(auto const& e : foo().range()) // OK

এটি অপের ক্ষেত্রে প্রয়োগ করা, এবং সামান্য কোড পর্যালোচনা

struct B {
    A m_a;
    A & a() { return m_a; }
};

এই সদস্য ফাংশনটি প্রকাশের মান বিভাগে পরিবর্তন করে: B()এটি একটি মূল্য, তবে B().a()মূল্য হয়। অন্যদিকে, B().m_aএকটি মূল্য। সুতরাং এর এই ধারাবাহিক করে শুরু করা যাক। এই কাজটি করার দুটি পদ্ধতি আছে:

struct B {
    A m_a;
    A &  a() &  { return m_a; }

    A && a() && { return std::move(m_a); }
    // or
    A    a() && { return std::move(m_a); }
};

দ্বিতীয় সংস্করণ, যেমন উপরে বলা হয়েছে, ওপিতে বিষয়টি ঠিক করবে।

অতিরিক্তভাবে, আমরা Bএর সদস্য ফাংশনগুলি সীমাবদ্ধ করতে পারি :

struct A {
    // [...]

    int * begin() & { return &v[0]; }
    int * end  () & { return &v[3]; }

    int v[3];
};

এটি ওপি-র কোডে কোনও প্রভাব ফেলবে না, যেহেতু :লুপের জন্য সীমা-ভিত্তিতে রেঞ্জের পরে অভিব্যক্তির ফলাফলটি একটি রেফারেন্স ভেরিয়েবলের সাথে আবদ্ধ। এবং এই পরিবর্তনশীল (এর beginএবং endসদস্য ফাংশনগুলিতে অ্যাক্সেসের জন্য ব্যবহৃত একটি এক্সপ্রেশন হিসাবে ) একটি মূল্য রয়েছে।

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

// using the OP's definition of `struct B`,
// or version 1, `A && a() &&;`

A&&      a = B().a(); // bug: binds directly, dangling reference
A const& a = B().a(); // bug: same as above
A        a = B().a(); // OK

A&&      a = B().m_a; // OK: extends the lifetime of the temporary

সি ++ ২ এ, আমি মনে করি যে আপনি এই (বা অনুরূপ) ইস্যুটি নিম্নরূপে কাজ করবেন বলে মনে করছেন:

for( B b = bar(); auto i : b.a() )

পরিবর্তে ওপি এর

for( auto i : bar().a() )

কর্মক্ষেত্রটি ম্যানুয়ালি নির্দিষ্ট করে যে এর জীবনকাল bহ'ল ফোর-লুপের পুরো ব্লক।

প্রস্তাব যা এই init-বিবৃতি প্রবর্তন করে

জীবন্ত উদাহরণ


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