অ্যাসাইনমেন্ট অপারেটর এবং `যদি (এটি! = & আরএসএস)` সরান `


126

কোনও শ্রেণীর অ্যাসাইনমেন্ট অপারেটরে আপনাকে সাধারণত যাচাই করা অবজেক্টটি ইনভোকেজ অবজেক্ট কিনা তা পরীক্ষা করে দেখার দরকার হয় যাতে আপনি জিনিসগুলি স্ক্রু আপ না করেন:

Class& Class::operator=(const Class& rhs) {
    if (this != &rhs) {
        // do the assignment
    }

    return *this;
}

মুভ অ্যাসাইনমেন্ট অপারেটরের জন্য আপনার কি একই জিনিস দরকার? কখনও কি এমন পরিস্থিতি রয়েছে যেখানে this == &rhsসত্য হবে?

? Class::operator=(Class&& rhs) {
    ?
}

12
জিজ্ঞাসা করা প্রশ্নাবলীর সাথে অপ্রাসঙ্গিক, এবং ঠিক যাতে নতুন ব্যবহারকারী যারা এই প্রশ্নটি টাইমলাইনের বাইরে পড়ে (কারণ আমি জানি শেठ ইতিমধ্যে এটি জানেন) ভুল ধারণা না পান, অনুলিপি অপারেটর প্রয়োগের সঠিক উপায় হ'ল অনুলিপি স্ব অ্যাসাইনমেন্ট এট-সবের জন্য যাচাই করার দরকার নেই।
অলোক সেভ করুন

5
@VaughnCato: A a; a = std::move(a);
Xeo

11
ব্যবহার @VaughnCato std::moveস্বাভাবিক। তারপরে অ্যালিজিং অ্যাকাউন্টে নিন, এবং আপনি যখন কল স্ট্যাকের অভ্যন্তরে গভীর হন এবং আপনার একটির রেফারেন্স Tএবং অন্যরকম রেফারেন্স থাকে T... আপনি কি এই মুহুর্তে পরিচয় যাচাই করতে যাচ্ছেন? আপনি কি প্রথম কল (বা কল) সন্ধান করতে চান যেখানে আপনি একই যুক্তিটি দু'বার পাস করতে পারবেন না এমন নথিপত্র স্থিতিশীলভাবে প্রমাণ করবে যে এই দুটি রেফারেন্সের নাম নেই? অথবা আপনি কেবল স্ব-কার্য-সম্পাদনা করবেন?
লুক ড্যান্টন

2
@ লুকড্যান্টন আমি অ্যাসাইনমেন্ট অপারেটরে একটি দৃ as়তা পছন্দ করব। যদি স্ট্যান্ড :: পদক্ষেপটি এমনভাবে ব্যবহার করা হত যে কোনও মূল্য নির্ধারণের স্ব-কার্য-সম্পাদনা দিয়ে শেষ করা সম্ভব হয়েছিল, আমি এটিকে একটি বাগ উল্লেখ করব যা ঠিক করা উচিত।
ভন ক্যাটো

4
@ ভোজনগেটো স্ব-অদল-বদল স্বাভাবিক হয় এমন এক জায়গার ভিতরে std::sortবা হয় std::shuffle- যে কোনও সময় আপনি প্রথমে পরীক্ষা না করেই কোনও অ্যারের তি iএবং jতম উপাদানটি অদলবদল করে যাচ্ছেন i != j। ( std::swapসরানো অ্যাসাইনমেন্টের ক্ষেত্রে কার্যকর করা হয়))
কুইকসপ্লসোন

উত্তর:


143

বাহ, এখানে পরিষ্কার করার মতো অনেক কিছুই আছে ...

প্রথমত, অনুলিপি অ্যাসাইনমেন্ট বাস্তবায়নের জন্য অনুলিপি এবং অদলবদল সর্বদা সঠিক উপায় নয়। প্রায় অবশ্যই ক্ষেত্রে dumb_arrayএটি একটি উপ-অনুকূল সমাধান।

ব্যবহারের কপি করুন এবং সোয়াপ জন্য dumb_arrayনীচে স্তর এ পূর্ণ বৈশিষ্ট্য সঙ্গে সবচেয়ে ব্যয়বহুল অপারেশন নির্বাণ একটি ক্লাসিক উদাহরণ। এটি এমন ক্লায়েন্টদের জন্য উপযুক্ত যা পুরো বৈশিষ্ট্যটি চায় এবং পারফরম্যান্সের জরিমানা দিতে রাজি হয়। তারা যা চায় ঠিক তা পায়।

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

উভয় ক্লায়েন্টকে সন্তুষ্ট করার চাবিকাঠি হ'ল নিম্নতম স্তরে দ্রুততম ক্রিয়াকলাপ তৈরি করা এবং তারপরে আরও ব্যয় করে পূর্ণ বৈশিষ্ট্যগুলির জন্য এর উপরে এপিআই যুক্ত করা। অর্থাৎ আপনার দৃ you় ব্যতিক্রম গ্যারান্টি দরকার, জরিমানা, আপনি এটির জন্য অর্থ প্রদান করেন। তোমার দরকার নেই? এখানে একটি দ্রুত সমাধান।

আসুন কংক্রিটটি পান: এখানে দ্রুত, মৌলিক ব্যতিক্রমের গ্যারান্টি হ'ল কপির জন্য অ্যাসাইনমেন্ট অপারেটর dumb_array:

dumb_array& operator=(const dumb_array& other)
{
    if (this != &other)
    {
        if (mSize != other.mSize)
        {
            delete [] mArray;
            mArray = nullptr;
            mArray = other.mSize ? new int[other.mSize] : nullptr;
            mSize = other.mSize;
        }
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }
    return *this;
}

ব্যাখ্যা:

আধুনিক হার্ডওয়্যারটিতে আপনি যে আরও ব্যয়বহুল জিনিসগুলি করতে পারেন তার মধ্যে একটি হ'ল to গাদা ট্রিপ এড়াতে আপনি যা কিছু করতে পারেন তা হ'ল সময় এবং প্রচেষ্টা ভালভাবে ব্যয় করা। এর গ্রাহকরা dumb_arrayপ্রায়শই একই আকারের অ্যারে নির্ধারণ করতে চাইতে পারেন। এবং যখন তারা করে, আপনার যা করতে হবে তা হ'ল একটি memcpy(আওতাভুক্ত std::copy)। আপনি একই আকারের একটি নতুন অ্যারে বরাদ্দ করতে চান না এবং তারপরে একই আকারের পুরানোটিকে বাতিল করতে চান না!

এখন আপনার ক্লায়েন্টদের জন্য যারা প্রকৃতপক্ষে শক্তিশালী ব্যতিক্রমী সুরক্ষা চান:

template <class C>
C&
strong_assign(C& lhs, C rhs)
{
    swap(lhs, rhs);
    return lhs;
}

অথবা আপনি যদি সি ++ 11 এ মুভ অ্যাসাইনমেন্টের সুবিধা নিতে চান তবে তা হওয়া উচিত:

template <class C>
C&
strong_assign(C& lhs, C rhs)
{
    lhs = std::move(rhs);
    return lhs;
}

যদি dumb_arrayক্লায়েন্টের মান গতি হয় তবে তাদের কল করা উচিত operator=। তাদের যদি শক্তিশালী ব্যতিক্রমী সুরক্ষা প্রয়োজন হয় তবে জেনেরিক অ্যালগরিদম রয়েছে যা তারা কল করতে পারেন যা বিভিন্ন ধরণের অবজেক্টগুলিতে কাজ করবে এবং কেবল একবার প্রয়োগ করা দরকার।

এখন মূল প্রশ্নে ফিরে আসুন (যা এই সময়ে একটি টাইপ-ও রয়েছে):

Class&
Class::operator=(Class&& rhs)
{
    if (this == &rhs)  // is this check needed?
    {
       // ...
    }
    return *this;
}

এটি আসলে একটি বিতর্কিত প্রশ্ন। কেউ কেউ হ্যাঁ বলবে, একেবারে, কেউ বলবে না।

আমার ব্যক্তিগত মতামত না, আপনার এই চেকের দরকার নেই।

নীতি:

যখন কোনও বস্তু কোনও মূল্যের রেফারেন্সের সাথে আবদ্ধ হয় তা দুটি জিনিসের মধ্যে একটি:

  1. একটি অস্থায়ী.
  2. কলকারী কোনও জিনিস আপনাকে বিশ্বাস করতে চায় এটি একটি অস্থায়ী।

যদি আপনার কাছে কোনও অবজেক্টের একটি রেফারেন্স থাকে যা সত্যিকারের অস্থায়ী হয় তবে সংজ্ঞা অনুসারে আপনার সেই অবজেক্টের একটি অনন্য রেফারেন্স রয়েছে। এটি সম্ভবত আপনার পুরো প্রোগ্রামের অন্য কোথাও উল্লেখ করা যায় না। অর্থাৎ this == &temporary সম্ভব নয়

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

Class&
Class::operator=(Class&& other)
{
    assert(this != &other);
    // ...
    return *this;
}

অর্থাত আপনি যদি হয় একটি স্ব রেফারেন্স পাস, এই ক্লায়েন্ট যে সংশোধন করা উচিত অংশ একটি বাগ।

সম্পূর্ণতার জন্য, এখানে একটি স্থানান্তর নিয়োগের অপারেটর dumb_array:

dumb_array& operator=(dumb_array&& other)
{
    assert(this != &other);
    delete [] mArray;
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

সরানো অ্যাসাইনমেন্টের সাধারণ ব্যবহারের ক্ষেত্রে, *thisএকটি স্থানান্তরিত অবজেক্ট হবে এবং তাই delete [] mArray;কোনও অপ-অফ হওয়া উচিত। এটি সমালোচনামূলক যে বাস্তবায়নগুলি নাল্প্টারের উপর যত তাড়াতাড়ি সম্ভব মুছুন।

সতর্কীকরণ:

কিছু তর্ক করবে যে swap(x, x)এটি একটি ভাল ধারণা, বা কেবল একটি প্রয়োজনীয় মন্দ। এবং এটি, যদি অদলবদল ডিফল্ট অদলবদলে যায় তবে এটি একটি স্ব-মুভ-অ্যাসাইনমেন্টের কারণ হতে পারে।

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

dumb_array& operator=(dumb_array&& other)
{
    assert(this != &other || mSize == 0);
    delete [] mArray;
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

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

<হালনাগাদ>

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

অনুলিপি নিয়োগের জন্য:

x = y;

একটির একটি পোস্ট-শর্ত yথাকা উচিত যে এর মান পরিবর্তন করা উচিত নয়। যখন &x == &yএই postcondition অনুবাদ তারপর: স্ব কপি নিয়োগ মান কোন প্রভাব থাকা উচিত x

স্থানান্তরের জন্য:

x = std::move(y);

একটির একটি পোস্ট-শর্ত থাকতে হবে যা yএকটি বৈধ কিন্তু অনির্ধারিত রাজ্য আছে। যখন &x == &yতারপর এই postcondition অনুবাদ: xএকটি বৈধ কিন্তু অনির্দিষ্ট রাষ্ট্র হয়েছে। অর্থাত্ স্ব-মুভ অ্যাসাইনমেন্টটি কোনও অপ-অফ হতে হবে না। তবে এটি ক্রাশ হওয়া উচিত নয়। এই পোস্ট-শর্তটি swap(x, x)কেবল কাজ করার অনুমতি দেওয়ার সাথে সামঞ্জস্যপূর্ণ :

template <class T>
void
swap(T& x, T& y)
{
    // assume &x == &y
    T tmp(std::move(x));
    // x and y now have a valid but unspecified state
    x = std::move(y);
    // x and y still have a valid but unspecified state
    y = std::move(tmp);
    // x and y have the value of tmp, which is the value they had on entry
}

উপরের কাজগুলি যতক্ষণ x = std::move(x)না ক্রাশ হয়। এটি xকোনও বৈধ তবে অনির্ধারিত অবস্থায় ছেড়ে দিতে পারে ।

এটি dumb_arrayঅর্জনের জন্য আমি মুভ অ্যাসাইনমেন্ট অপারেটরটিকে প্রোগ্রাম করার জন্য তিনটি উপায় দেখছি :

dumb_array& operator=(dumb_array&& other)
{
    delete [] mArray;
    // set *this to a valid state before continuing
    mSize = 0;
    mArray = nullptr;
    // *this is now in a valid state, continue with move assignment
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

উপরোক্ত প্রয়োগটি স্ব-কার্যনির্বাহিতা সহ্য করে, *thisএবং otherস্ব-পদক্ষেপের অ্যাসাইনমেন্টের পরে শূন্য আকারের অ্যারে হিসাবে শেষ হয়, এর আসল মান যাই হোক না কেন *this। ্রফ.

dumb_array& operator=(dumb_array&& other)
{
    if (this != &other)
    {
        delete [] mArray;
        mSize = other.mSize;
        mArray = other.mArray;
        other.mSize = 0;
        other.mArray = nullptr;
    }
    return *this;
}

উপরোক্ত প্রয়োগটি কপিরাইট অ্যাসাইনমেন্ট অপারেটরকে যেমন কোনও অনি-অফ করে তোলে সেভাবে স্ব-কার্য নিয়োগকে সহ্য করে। এটিও ঠিক আছে।

dumb_array& operator=(dumb_array&& other)
{
    swap(other);
    return *this;
}

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

প্রথমটির দাম দুটি অতিরিক্ত স্টোর। দ্বিতীয়টির দাম একটি পরীক্ষা-ও-শাখা। দুটোই কাজ করে। উভয়ই সি ++ 11 স্ট্যান্ডার্ডে টেবিল 22 মুভআসাইনেজেবল প্রয়োজনীয়তার সমস্ত প্রয়োজনীয়তা পূরণ করে। তৃতীয়টি অ-স্মৃতি-সংস্থান-উদ্বেগের মডুলগুলিতেও কাজ করে।

তিনটি বাস্তবায়নের ক্ষেত্রে হার্ডওয়্যারের উপর নির্ভর করে বিভিন্ন ব্যয় হতে পারে: একটি শাখা কত ব্যয়বহুল? প্রচুর রেজিস্ট্রার আছে নাকি খুব কম?

গ্রহণযোগ্যতাটি হ'ল স্ব-মুভ-অ্যাসাইনমেন্ট, স্ব-অনুলিপি-অ্যাসাইনমেন্টের বিপরীতে, বর্তমান মান সংরক্ষণ করতে হবে না।

</হালনাগাদ>

লাক ড্যান্টনের মন্তব্য দ্বারা অনুপ্রাণিত হয়ে একটি চূড়ান্ত (আশাবাদী) সম্পাদনা:

যদি আপনি এমন একটি উচ্চ স্তরের শ্রেণি লিখছেন যা সরাসরি মেমরি পরিচালনা করে না (তবে এর বেসগুলি বা সদস্যগুলি থাকতে পারে) তবে সরানো কার্যনির্বাহনের সর্বোত্তম বাস্তবায়ন প্রায়শই:

Class& operator=(Class&&) = default;

এটি প্রতিটি বেস এবং প্রতিটি সদস্যকে পালাক্রমে বরাদ্দ করবে এবং একটি this != &otherচেক অন্তর্ভুক্ত করবে না । কোনও বেসরকারীকে আপনার ঘাঁটি এবং সদস্যদের মধ্যে রক্ষণাবেক্ষণের প্রয়োজন নেই বলে ধরে নিলে এটি আপনাকে সর্বোচ্চ পারফরম্যান্স এবং বেসিক ব্যতিক্রমী সুরক্ষা দেবে। আপনার ক্লায়েন্টদের শক্তিশালী ব্যতিক্রম সুরক্ষার দাবিতে তাদের দিকে নির্দেশ করুন strong_assign


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

হ্যাঁ, আমি অবশ্যই অনুলিপি-অদলবদল কখনই ব্যবহার করি না কারণ এটি এমন ক্লাসগুলির জন্য সময় নষ্ট করে যা সংস্থান এবং জিনিসগুলি পরিচালনা করে (কেন আপনার সমস্ত ডেটারের অন্য একটি অনুলিপি তৈরি করবেন?)। এবং ধন্যবাদ, এটি আমার প্রশ্নের উত্তর দেয়।
শেঠ কার্নেগি

5
পরামর্শ যে পদক্ষেপ-কার্যভার FROM-স্ব উচিত Downvoted কি কখনো জাহির-ফেল অথবা একটি "অনির্দিষ্ট" ফল। স্বীকৃতি-থেকে-স্বাক্ষর আক্ষরিকভাবে ডান পেতে সবচেয়ে সহজ কেস । যদি আপনার ক্লাসটি ক্রাশ হয়ে যায় std::swap(x,x), তবে আরও জটিল ক্রিয়াকলাপগুলি সঠিকভাবে পরিচালনা করার জন্য কেন আমি এটি বিশ্বাস করব?
কুইকসপ্লসোন

1
@ কিউকপ্লসোন: আমি আমার জবাবটি আপডেটে উল্লিখিত হিসাবে, দৃsert়-ব্যর্থতা নিয়ে আপনার সাথে একমত হতে এসেছি। যতদূর std::swap(x,x)যায়, এটি কেবল তখনই কাজx = std::move(x) করে যখন অনির্দিষ্ট ফলাফল তৈরি করে। চেষ্টা করে দেখুন! তুমি আমাকে বিশ্বাস করতে হবে না।
হাওয়ার্ড হিন্যান্ট

@ হাওয়ার্ডহিন্যান্ট ভাল পয়েন্ট, swapযে কোনও স্থানে-সক্ষম-স্থিতিতে যতক্ষণ x = move(x)পাতাগুলি কাজ করে x। এবং std::copy/ std::moveঅ্যালগরিদমগুলি সংজ্ঞায়িত করা হয়েছে যাতে ইতিমধ্যে নো-অপশন অনুলিপিগুলিতে অনির্ধারিত আচরণ তৈরি করা যায় (আউট; 20 বছর বয়সী memmoveতুচ্ছ ঘটনাটি সঠিক পায় তবে std::moveতা হয় না!)। সুতরাং আমি অনুমান করি যে আমি এখনও স্ব-নিয়োগের জন্য "স্ল্যাম ডঙ্ক" ভাবিনি। তবে স্পষ্টতই স্ব-কার্য-সম্পাদনা হ'ল এমন কিছু যা বাস্তব কোডে অনেক কিছু ঘটে, মানক এটি আশীর্বাদ করেছে কি না।
কুক্সপ্লসোন

11

প্রথমে আপনি মুভ-এসাইনমেন্ট অপারেটরের ভুল স্বাক্ষর পেয়েছেন। যেহেতু উত্স উত্স থেকে উত্সগুলি চুরি করে সরস, উত্সটি একটি অ const-মূল্য-রেফারেন্স হতে হবে।

Class &Class::operator=( Class &&rhs ) {
    //...
    return *this;
}

মনে রাখবেন আপনি এখনও একটি (অ মাধ্যমে আসতে const) -value রেফারেন্স।

উভয় প্রকারের সরাসরি অ্যাসাইনমেন্টের জন্য, মানটি স্ব-অ্যাসাইনমেন্টের জন্য যাচাই করা হয় না, তবে এটি নিশ্চিত করার জন্য যে কোনও স্ব-কার্য-ক্রয় ক্রাশ-বার্ন হওয়ার কারণ নয়। সাধারণত, স্পষ্টভাবে কেউই ফোন করে না x = xবা y = std::move(y)কল করে না, তবে এলিয়াসিং, বিশেষত একাধিক ফাংশনের মাধ্যমে, নেতৃত্ব দিতে পারে a = bবা c = std::move(d)স্ব-কার্যনির্বাহী হতে পারে । স্ব-কার্যনির্বাহনের জন্য একটি সুস্পষ্ট চেক, অর্থাত্ this == &rhsআত্ম-কার্যনির্বাহার সুরক্ষা নিশ্চিত করার একমাত্র উপায় হলে এটি ফাংশনের মাংসকে এড়িয়ে যায়। তবে এটি একটি নিকৃষ্টতম উপায়, যেহেতু এটি একটি (আশাকরি) বিরল কেসটিকে অনুকূল করে তোলে যখন এটি আরও সাধারণ ক্ষেত্রে (শাখা-প্রশাখার কারণ এবং সম্ভবত ক্যাশে মিস করার কারণে) বিরোধী-অনুকূলকরণ।

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

অন্য একটি উত্তরদাতা থেকে পরিবর্তিত, একটি উদাহরণ তৈরি করা যাক:

dumb_array& dumb_array::operator=(const dumb_array& other)
{
    if (mSize != other.mSize)
    {
        delete [] mArray;
        mArray = nullptr;  // clear this...
        mSize = 0u;        // ...and this in case the next line throws
        mArray = other.mSize ? new int[other.mSize] : nullptr;
        mSize = other.mSize;
    }
    std::copy(other.mArray, other.mArray + mSize, mArray);
    return *this;
}

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

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

আসুন এই একই ধরণের জন্য একটি মুভ-অ্যাসাইনমেন্টটি দেখুন:

class dumb_array
{
    //...
    void swap(dumb_array& other) noexcept
    {
        // Just in case we add UDT members later
        using std::swap;

        // both members are built-in types -> never throw
        swap( this->mArray, other.mArray );
        swap( this->mSize, other.mSize );
    }

    dumb_array& operator=(dumb_array&& other) noexcept
    {
        this->swap( other );
        return *this;
    }
    //...
};

void  swap( dumb_array &l, dumb_array &r ) noexcept  { l.swap( r ); }

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

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

মুভ-অ্যাসাইনমেন্টের এই প্রথম সংস্করণটি মৌলিক চুক্তিটি পূরণ করে। উত্সের সংস্থান চিহ্নগুলি গন্তব্য অবজেক্টে স্থানান্তরিত হয়। পুরানো সংস্থানগুলি ফাঁস হবে না যেহেতু উত্স অবজেক্ট এখন সেগুলি পরিচালনা করে। এবং উত্স অবজেক্টটি ব্যবহারযোগ্য স্থানে রেখে গেছে যেখানে অ্যাসাইনমেন্ট এবং ধ্বংস সহ আরও ক্রিয়াকলাপ প্রয়োগ করা যেতে পারে।

নোট করুন যে এই মুভ-অ্যাসাইনমেন্টটি স্ব-অ্যাসাইনমেন্টের জন্য স্বয়ংক্রিয়ভাবে নিরাপদ, কারণ swapকল। এটি দৃ strongly়ভাবে ব্যতিক্রম নিরাপদ। সমস্যাটি অপ্রয়োজনীয় সংস্থান সংরক্ষণের। গন্তব্যটির জন্য পুরানো সংস্থানগুলি ধারণাগতভাবে আর প্রয়োজন হয় না, তবে এখানে এখনও সেগুলি কেবলমাত্র তাই উত্স অবজেক্টটি বৈধ থাকতে পারে। যদি উত্স অবজেক্টের নির্ধারিত ধ্বংসটি অনেক দূরে থাকে তবে আমরা যদি সম্পদের স্থান নষ্ট করে দিই বা আরও খারাপ হয় তবে যদি মোট সম্পদের স্থান সীমাবদ্ধ থাকে এবং (নতুন) উত্স অবজেক্টটি আনুষ্ঠানিকভাবে মারা যাওয়ার আগে অন্যান্য সংস্থান পিটিশনগুলি ঘটবে।

এই সমস্যাটি মুভ-অ্যাসাইনমেন্টের সময় স্ব-লক্ষ্যবস্তু সম্পর্কিত বিতর্কিত গুরু গুরুের পরামর্শের কারণ। দীর্ঘস্থায়ী সংস্থান ছাড়াই মুভ-অ্যাসাইনমেন্ট লেখার উপায়টি হ'ল:

class dumb_array
{
    //...
    dumb_array& operator=(dumb_array&& other) noexcept
    {
        delete [] this->mArray;  // kill old resources
        this->mArray = other.mArray;
        this->mSize = other.mSize;
        other.mArray = nullptr;  // reset source
        other.mSize = 0u;
        return *this;
    }
    //...
};

উত্সটি ডিফল্ট শর্তগুলিতে পুনরায় সেট করা হয়েছে, যখন পুরানো গন্তব্য সংস্থানগুলি ধ্বংস হয়ে যায়। স্ব-নিয়োগের ক্ষেত্রে, আপনার বর্তমান বস্তুটি আত্মহত্যা শেষ করে। এর চারপাশের প্রধান উপায় হ'ল একটি if(this != &other)ব্লক সহ অ্যাকশন কোডটি ঘিরে দেওয়া , বা এটি স্ক্রু করা এবং ক্লায়েন্টদের একটি assert(this != &other)প্রাথমিক লাইন খেতে দেওয়া (যদি আপনি সুন্দর বোধ করছেন)।

বিকল্পটি হ'ল কীভাবে অনুলিপি-অ্যাসাইনমেন্ট ছাড়াই অনুলিপি-অ্যাসাইনমেন্টকে নিরাপদভাবে ব্যতিক্রম নিরাপদ করা যায় এবং এটিকে মুভিং-অ্যাসাইনমেন্টে প্রয়োগ করা যায়:

class dumb_array
{
    //...
    dumb_array& operator=(dumb_array&& other) noexcept
    {
        dumb_array  temp{ std::move(other) };

        this->swap( temp );
        return *this;
    }
    //...
};

যখন otherএবং thisস্বতন্ত্র হয়, otherসরানো দ্বারা খালি হয় tempএবং সেভাবেই থাকে। তারপরে মূলত thisসংস্থানগুলি গ্রহণ করার tempসময় তার পুরানো সংস্থানগুলি হারিয়ে যায় other। তারপরে পুরানো সম্পদগুলি thisকখন মারা যায় temp

যখন স্ব-নিয়োগ ঘটে, এর খালি otherকরার tempবাঁধিবার জিনিসপত্র thisহিসাবে ভাল। তারপরে টার্গেট অবজেক্টটি কখন tempএবং thisঅদলবদল করে তার সংস্থান ফিরে পায় । মৃত্যুর tempদাবি একটি খালি বস্তু, যা ব্যবহারিকভাবে একটি অন-আপ হওয়া উচিত। this/ otherবস্তু তার সম্পদ রাখে।

মুভ-অ্যাসাইনমেন্টটি যতক্ষণ না মুভ-কনস্ট্রাকশন এবং অদলবদল হয় ততক্ষণ নিক্ষেপ করা উচিত। স্ব-নিয়োগের সময় সুরক্ষিত থাকার ব্যয় হ'ল নিম্ন-স্তরের প্রকারের জন্য আরও কয়েকটি নির্দেশাবলী the


deleteআপনার দ্বিতীয় ব্লকের কোডে ফোন করার আগে কোনও মেমরি বরাদ্দ করা হয়েছিল কিনা তা পরীক্ষা করে দেখার দরকার কি ?
ব্যবহারকারী 3728501

3
আপনার দ্বিতীয় কোডের নমুনা, স্ব-এসাইনমেন্ট চেক ছাড়াই অনুলিপি-অ্যাসাইনমেন্ট অপারেটরটি ভুল। std::copyউত্স এবং গন্তব্য সীমার ওভারল্যাপ হয়ে থাকলে (যখন তারা মিলে তখন কেস সহ) অপরিজ্ঞাত আচরণের কারণ হয়। সি ++ 14 [alg.copy] / 3 দেখুন।
এমএম

6

আমি তাদের শিবিরে আছি যারা স্ব-নিয়োগের নিরাপদ অপারেটর চায়, তবে বাস্তবায়নে স্ব-কার্য-নির্ধারণের চেক লিখতে চাই না operator=। এবং আসলে আমি operator=কিছুটা প্রয়োগ করতেও চাই না , আমি চাই ডিফল্ট আচরণটি 'বাক্সের বাইরে' ' সেরা বিশেষ সদস্যরা যারা নিখরচায় আসে।

বলা হচ্ছে, স্ট্যান্ডার্ডে উপস্থিত মুভাসেগনেবল প্রয়োজনীয়তাগুলি নীচে বর্ণিত হয়েছে (১ 17..6.৩.১ থেকে টেমপ্লেট আর্গুমেন্ট প্রয়োজনীয়তা [ইউটিলিটি.আরগ। রিকুইরিমেন্টস], এন 3290):

এক্সপ্রেশন রিটার্ন টাইপ রিটার্ন মান পোস্ট-শর্ত
t = rv T & tt অ্যাসাইনমেন্টের আগে আরভি মানের সাথে সমান

স্থানধারকগুলি যেখানে বর্ণিত হয়েছে: " t[টি] টি টাইপের সংশোধনযোগ্য লভ্যালু;" এবং " rvটাইপ টি এর মূল মূল্য;"। নোট করুন যে স্ট্যান্ডার্ড লাইব্রেরির টেমপ্লেটগুলিতে আর্গুমেন্ট হিসাবে ব্যবহৃত ধরণের প্রয়োজনীয়তাগুলি সেগুলি হ'ল তবে স্ট্যান্ডার্ডে অন্য কোথাও সন্ধান করলাম আমি লক্ষ্য করেছি যে স্থানান্তরের অ্যাসাইনমেন্টের প্রতিটি প্রয়োজনীয়তা এটির মতো।

এর অর্থ a = std::move(a)'নিরাপদ' থাকতে হবে। আপনার যা প্রয়োজন তা যদি একটি পরিচয় পরীক্ষা (যেমন this != &other) হয় তবে এটির জন্য যান, না হলে আপনি নিজের বস্তুগুলিতেও রাখতে সক্ষম হবেন না std::vector! (যদি না আপনি সেই সদস্য / অপারেশনগুলি ব্যবহার না করেন যার জন্য মুভএইসাইনএবলের প্রয়োজন হয়; তবে এটি মনে রাখবেন না)) লক্ষ্য করুন যে পূর্ববর্তী উদাহরণ সহকারে a = std::move(a), তাহলে this == &otherঅবশ্যই তা ধরে রাখা হবে।


আপনি কী ব্যাখ্যা করতে পারবেন কীভাবে a = std::move(a)কাজ না করে কোনও শ্রেণি কাজ করে না std::vector? উদাহরণ?
পল জে লুকাস

@ PaulJ.Lucas কল করার std::vector<T>::eraseঅনুমতি Tনেই মূয়াভএসাইনগযোগ্য না হলে unless (একদিকে আইআইআরসি হিসাবে কিছু মুভএইসনেবলযোগ্য প্রয়োজনীয়তা সি ++ 14 এর পরিবর্তে মুভইনসারেটেবলে শিথিল করা হয়েছিল))
ড্যান্টন

ঠিক আছে, তাই Tমুভাসেগনেবল হতে হবে, তবে কেন erase()কখনও কোনও উপাদানকে নিজের দিকে সরানোর উপর নির্ভর করবে ?
পল জে লুকাস

@ পলজে। লুকাস এই প্রশ্নের কোনও সন্তোষজনক উত্তর নেই। 'চুক্তি ভঙ্গ করবেন না' - এ সবকিছুই ফুটে উঠেছে।
লুক ড্যান্টন

2

আপনার বর্তমান operator=ফাংশনটি যেমন লেখা হয়েছে, যেহেতু আপনি মূল্য-রেফারেন্স যুক্তি তৈরি করেছেন const, আপনি পয়েন্টারগুলিকে "চুরি" করতে এবং আগত মূল্যবোধের রেফারেন্সের মান পরিবর্তন করতে পারবেন না ... আপনি কেবল এটি পরিবর্তন করতে পারবেন না, আপনি শুধুমাত্র এটি থেকে পড়তে পারে। আমি কেবল একটি ইস্যু দেখতে পাচ্ছি যদি আপনি deleteআপনার thisঅবজেক্টে পয়েন্টার ইত্যাদিতে কল করা শুরু করেন যেমন আপনি একটি সাধারণ লভু-রেফারেন্স operator=পদ্ধতিতে করেন তবে এই ধরণের রুল্যু-সংস্করণটির বিন্দুটি পরাভূত হয় ... এটি হ'ল মূলত একই ক্রিয়াকলাপটি সাধারণত constঅলভ্য operator=পদ্ধতিতে রেখে একই ক্রিয়াকলাপ করতে রালু সংস্করণটি ব্যবহার করতে অপ্রয়োজনীয় বলে মনে হয় ।

এখন আপনি যদি operator=const-মান-রেফারেন্স নিতে নিজের সংজ্ঞা দিয়ে থাকেন , তবে আপনি যদি thisঅস্থায়ী না হয়ে ইচ্ছাকৃতভাবে কোনও মূল্যের রেফারেন্স ফিরিয়ে দেন এমন কোনও ফাংশনে অবজেক্টটি পাস করেন তবে আমি চেকের প্রয়োজন দেখাতে পারার একমাত্র উপায় ।

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

struct A; //defines operator=(A&& rhs) where it will "steal" the pointers
          //of rhs and set the original pointers of rhs to NULL

A&& operator+(A& rhs, A&& lhs)
{
    //...code

    return std::move(rhs);
}

A&& operator+(A&& rhs, A&&lhs)
{
    //...code

    return std::move(rhs);
}

int main()
{
    A a;

    a = (a + A()) + A(); //calls operator=(A&&) with reference bound to a

    //...rest of code
}

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


মনে রাখবেন যে "a = std :: পদক্ষেপ (ক)" এই পরিস্থিতিটি হবার জন্য একটি তুচ্ছ উপায়। আপনার উত্তর যদিও বৈধ।
ভন ক্যাটো

1
পুরোপুরি সম্মত হ'ল এটি সহজতম উপায়, যদিও আমি মনে করি বেশিরভাগ লোক ইচ্ছাকৃতভাবে এটি করবে না :-) ... তবে মনে রাখবেন যে যদি মূলসূত্র-রেফারেন্স হয় constতবে আপনি কেবল এটি থেকে পড়তে পারেন, তাই কেবল প্রয়োজন আপনি যদি সিদ্ধান্ত নেন যে আপনি operator=(const T&&)একই পদ্ধতি পুনরায় আরম্ভ করার সিদ্ধান্ত নিয়েছেন thisযা আপনি operator=(const T&)অদলবদল শৈলীর অপারেশন (যেমন, গভীর কপি তৈরির পরিবর্তে পয়েন্টার ইত্যাদি চুরি) না করে একটি সাধারণ পদ্ধতিতে করবেন।
জেসন

1

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

unique_ptr& operator=(unique_ptr&& x) {
  delete ptr_;
  ptr_ = x.ptr_;
  x.ptr_ = nullptr;
  return *this;
}

আপনি যদি স্কট মায়ার্সকে এটি ব্যাখ্যা করে দেখেন তবে তিনি অনুরূপ কিছু করেন। (আপনি যদি ঘোরাফেরা করেন কেন অদলবদল করবেন না - এতে একটি অতিরিক্ত লেখার দরকার রয়েছে)। এবং এটি স্ব নিয়োগের জন্য নিরাপদ নয়।

কখনও কখনও এটি দুর্ভাগ্যজনক। সমস্ত সংখ্যক ভেক্টর থেকে সরে যাওয়ার কথা বিবেচনা করুন:

src.erase(
  std::partition_copy(src.begin(), src.end(),
                      src.begin(),
                      std::back_inserter(even),
                      [](int num) { return num % 2; }
                      ).first,
  src.end());

এটি পূর্ণসংখ্যার জন্য ঠিক আছে তবে আমি বিশ্বাস করি না আপনি সরানো শব্দার্থক শব্দটির সাহায্যে এই কাজের মতো কিছু তৈরি করতে পারেন।

উপসংহারে: অবজেক্টে সরানো অ্যাসাইনমেন্ট নিজেই ঠিক নেই এবং আপনাকে এটির জন্য নজরদারি করতে হবে।

ছোট আপডেট।

  1. আমি হাওয়ার্ডের সাথে একমত নই, এটি একটি খারাপ ধারণা, তবে এখনও - আমি মনে করি "মুভিড আউট" অবজেক্টগুলির স্ব-মুভ অ্যাসাইনমেন্টটি কাজ করা উচিত, কারণ swap(x, x)কাজ করা উচিত। অ্যালগরিদমগুলি এই জিনিসগুলি ভালবাসেন! কোনও কোণার কেস যখন কাজ করে তখন এটি সর্বদা দুর্দান্ত। (এবং আমি এখনও এমন কোনও মামলা দেখতে পাইনি যেখানে এটি নিখরচায় নয়। এর অর্থ এই নয় যে এটি উপস্থিত নেই)।
  2. Libc ++ এ এইভাবে অনন্য_আপনার নির্ধারণ করা কার্যকর হয়: unique_ptr& operator=(unique_ptr&& u) noexcept { reset(u.release()); ...} স্ব-পদক্ষেপের জন্য এটি নিরাপদ for
  3. মূল নির্দেশিকাগুলি মনে করে যে এটি নিজের থেকে সরানো কার্য সম্পাদন করা ঠিক হবে।

0

এমন একটি পরিস্থিতি রয়েছে যা (এটি == আরএইচএস) আমি ভাবতে পারি। এই বিবৃতিটির জন্য: মাইক্লাস আপত্তি; স্টাড :: চাল (আপত্তি) = স্টাড :: চাল (আপত্তি)


মাইক্লাস আপত্তি; std :: اقدام (আপত্তি) = স্টাড :: চাল (আপত্তি);
লিটল_মন্সটার
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.