আজীবন আক্রমণকারীদের বনাম বনাম পদার্থ পদার্থের উদ্দেশ্য


13

আমি যখন C ++ অনেক আগে শিখেছি তখন আমার কাছে দৃ strongly়ভাবে জোর দেওয়া হয়েছিল যে সি ++ এর পয়েন্টের অংশটি হ'ল লুপগুলিতে যেমন "লুপ-ইনগ্রেন্ট" থাকে, ক্লাসগুলিতেও বস্তুর আজীবনের সাথে যুক্ত থাকে - জিনিসগুলি সত্য হওয়া উচিত যতক্ষণ অবজেক্টটি বেঁচে থাকে কনস্ট্রাক্টর দ্বারা প্রতিষ্ঠিত হওয়া উচিত এবং পদ্ধতিগুলি দ্বারা সংরক্ষণ করা উচিত। আক্রমণকারীদের কার্যকর করতে সহায়তা করার জন্য এনক্যাপসুলেশন / অ্যাক্সেস নিয়ন্ত্রণ রয়েছে। RAII হ'ল একটি জিনিস যা আপনি এই ধারণার সাথে করতে পারেন।

সি ++ 11 যেহেতু এখন আমাদের কাছে মুভ সিমানটিক রয়েছে। যে শ্রেণীর চলন সমর্থন করে, কোনও বস্তু থেকে সরানো তার জীবনকালকে আনুষ্ঠানিকভাবে শেষ করে না - এই পদক্ষেপটি কিছু "বৈধ" অবস্থায় রেখে যাওয়ার কথা।

ক্লাস ডিজাইনের ক্ষেত্রে, আপনি যদি এটি ডিজাইন করেন তবে কি খারাপ অভ্যাসটি বর্গের আক্রমণকারীদের কেবলমাত্র এমন স্থান পর্যন্ত সংরক্ষণ করা যায় যেটি থেকে স্থানান্তরিত হয়েছিল? বা এটি ঠিক আছে যদি এটি আপনাকে এটিকে আরও দ্রুত যেতে দেয়।

এটিকে কংক্রিট করার জন্য, ধরুন আমার কাছে অনুলিপিযোগ্য তবে চলনযোগ্য রিসোর্স ধরণের রয়েছে:

class opaque {
  opaque(const opaque &) = delete;

public:
  opaque(opaque &&);

  ...

  void mysterious();
  void mysterious(int);
  void mysterious(std::vector<std::string>);
};

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

class copyable_opaque {
  std::shared_ptr<opaque> o_;

  copyable_opaque() = delete;
public:
  explicit copyable_opaque(opaque _o)
    : o_(std::make_shared<opaque>(std::move(_o)))
  {}

  void operator()() { o_->mysterious(); }
  void operator()(int i) { o_->mysterious(i); }
  void operator()(std::vector<std::string> v) { o_->mysterious(v); }
};

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

যাইহোক, যদি অবজেক্টটি সরানো হয় তবে o_তার পরে কিছুই দেখানো হবে না। এবং এই বিন্দুটির পরে, যে কোনও পদ্ধতিতে কলিংয়ের operator()ফলে ইউবি / ক্রাশ হবে।

যদি বস্তুটি কখনই এখান থেকে সরানো হয় না, তবে ড্যান্টারের কল পর্যন্ত আক্রমণকারীটি সংরক্ষণ করা হবে।

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

থটস:

  1. জম্বি অবজেক্টগুলি তৈরি করতে এটি সি ++ এ সাধারণত খারাপ ফর্ম যা আপনি তাদের স্পর্শ করলে বিস্ফোরিত হয়।
    আপনি যদি কিছু অবজেক্ট তৈরি করতে না পারেন, আক্রমণকারীদের প্রতিষ্ঠা করতে না পারেন তবে ctor থেকে একটি ব্যতিক্রম নিক্ষেপ করুন। যদি আপনি কোনও পদ্ধতিতে আক্রমণকারীদের সংরক্ষণ করতে না পারেন তবে কোনওভাবে কোনও ত্রুটি সংকেত করুন এবং রোল-ব্যাক করুন। সরানো-থেকে আসা বস্তুর জন্য এটি আলাদা হওয়া উচিত?

  2. শিরোনামে "এই বস্তুটি সরিয়ে নেওয়ার পরে, কেবল এটি নথিভুক্ত করার পক্ষে কি যথেষ্ট?" শিরোনামে এটি ধ্বংস করা ছাড়া এটির সাথে অন্য কিছু করা অবৈধ (ইউবি)?

  3. এটি প্রতিটি পদ্ধতির কলটিতে বৈধ কিনা তা ধারাবাহিকভাবে দাবি করা আরও ভাল?

তাই ভালো:

class copyable_opaque {
  std::shared_ptr<opaque> o_;

  copyable_opaque() = delete;
public:
  explicit copyable_opaque(opaque _o)
    : o_(std::make_shared<opaque>(std::move(_o)))
  {}

  void operator()() { assert(o_); o_->mysterious(); }
  void operator()(int i) { assert(o_); o_->mysterious(i); }
  void operator()(std::vector<std::string> v) { assert(o_); o_->mysterious(v); }
};

দাবিগুলি আচরণের যথেষ্ট পরিমাণে উন্নতি করে না এবং এগুলি একটি ধীরগতির কারণ হয়। যদি আপনার প্রকল্পটি "রিলিজ বিল্ড / ডিবাগ বিল্ড" স্কিমটি ব্যবহার না করে কেবল সর্বদা দৃser়তার সাথে চলমান থাকে, তবে আমি অনুমান করি এটি আরও আকর্ষণীয়, যেহেতু আপনি রিলিজ বিল্ডের চেকগুলির জন্য অর্থ প্রদান করেন না। আপনার যদি ডিবাগ বিল্ডগুলি না থাকে তবে এটি বেশ অপ্রত্যাশিত বলে মনে হয়।

  1. ক্লাসটি অনুলিপিযোগ্য করা ভাল তবে কি অচল নয়?
    এটিও খারাপ বলে মনে হচ্ছে এবং এটি একটি পারফরম্যান্স হিটের কারণ, তবে এটি "আক্রমণকারী" ইস্যুকে সোজা উপায়ে সমাধান করে।

আপনি এখানে প্রাসঙ্গিক "সেরা অনুশীলন" হিসাবে বিবেচনা করবেন?



উত্তর:


20

জম্বি অবজেক্টগুলি তৈরি করতে এটি সি ++ এ সাধারণত খারাপ ফর্ম যা আপনি তাদের স্পর্শ করলে বিস্ফোরিত হয়।

তবে আপনি যা করছেন তা নয়। আপনি একটি "জম্বি অবজেক্ট" তৈরি করছেন যা ভুলভাবে স্পর্শ করলে এটি বিস্ফোরিত হবে । যা চূড়ান্তভাবে অন্য কোনও রাষ্ট্র-ভিত্তিক পূর্বশর্তের চেয়ে আলাদা নয়।

নিম্নলিখিত ফাংশন বিবেচনা করুন:

void func(std::vector<int> &v)
{
  v[0] = 5;
}

এই ফাংশনটি কি নিরাপদ? না; ব্যবহারকারী একটি খালি পাস করতে পারেন vector। সুতরাং ফাংশনটির একটি ডি-ফ্যাক্টো পূর্বশর্ত রয়েছে যার vএতে কমপক্ষে একটি উপাদান রয়েছে। যদি এটি না হয়, আপনি কল করার পরে আপনি ইউবি পাবেন func

সুতরাং এই ফাংশনটি "নিরাপদ" নয়। তবে এর অর্থ এই নয় যে এটি ভেঙে গেছে। এটি কেবলমাত্র তখনই ভাঙ্গা হবে যদি এটি ব্যবহার করে কোড পূর্বশর্ত লঙ্ঘন করে। হতে পারে funcএকটি স্ট্যাটিক ফাংশন যা অন্যান্য ফাংশনগুলির প্রয়োগে সহায়ক হিসাবে ব্যবহৃত হয়। এমনভাবে স্থানীয়ভাবে বানানো, কেউ এটিকে পূর্ব শর্তগুলি লঙ্ঘনকারী উপায়ে কল করবে না।

নেমস্পেস-স্কোপড বা ক্লাসের সদস্যরা যে কোনও কার্যক্রমে তাদের চালিত মানের হিসাবে তার প্রত্যাশা থাকবে। যদি এই পূর্বশর্তগুলি পূরণ না করা হয়, তবে ফাংশনগুলি ব্যর্থ হবে, সাধারণত ইউবির সাথে।

সি ++ স্ট্যান্ডার্ড লাইব্রেরি একটি "বৈধ-তবে-অনির্দিষ্ট" বিধিটিকে সংজ্ঞায়িত করে। এটি বলে যে, যদি না অন্যথায় স্ট্যান্ডার্ডটি না বলে, প্রতিটি বস্তু যা থেকে সরানো হয়েছে তা বৈধ হবে (এটি সেই ধরণের একটি আইনী অবজেক্ট), তবে সেই বস্তুর নির্দিষ্ট অবস্থা নির্দিষ্ট করা হয়নি। স্থানান্তরিত থেকে কয়টি উপাদান থাকে vector? এটা বলে না।

এর অর্থ হ'ল আপনি কোনও ফাংশনটিতে কল করতে পারবেন না যার কোনও পূর্ব শর্ত রয়েছে। কমপক্ষে একটি উপাদান vector::operator[]রয়েছে এমন পূর্বশর্ত vectorরয়েছে। যেহেতু আপনি রাজ্যের অবস্থা জানেন না তাই আপনি vectorএটি কল করতে পারবেন না। funcপ্রথমটি vectorখালি নয় তা যাচাই না করে কল করা ছাড়া আর ভাল হবে না ।

তবে এর অর্থ হ'ল পূর্বশর্ত না থাকা ফাংশনগুলি ঠিক আছে are এটি পুরোপুরি আইনী সি ++ 11 কোড:

vector<int> v1 = {1, 2, 3, 4, 5};
vector<int> v2{std::move(v1)};
v1.assign({6, 7, 8, 9, 10});

vector::assignকোন পূর্বশর্ত আছে। এটি যে কোনও বৈধ vectorঅবজেক্ট, এমনকি যেখান থেকে সরানো হয়েছে তার সাথেও কাজ করবে ।

সুতরাং আপনি ভাঙ্গা এমন কোনও বস্তু তৈরি করছেন না। আপনি এমন একটি বিষয় তৈরি করছেন যাঁর অবস্থা অজানা।

আপনি যদি কিছু অবজেক্ট তৈরি করতে না পারেন, আক্রমণকারীদের প্রতিষ্ঠা করতে না পারেন তবে ctor থেকে একটি ব্যতিক্রম নিক্ষেপ করুন। যদি আপনি কোনও পদ্ধতিতে আক্রমণকারীদের সংরক্ষণ করতে না পারেন তবে কোনওভাবে কোনও ত্রুটি সংকেত করুন এবং রোল-ব্যাক করুন। সরানো-থেকে আসা বস্তুর জন্য এটি আলাদা হওয়া উচিত?

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

দুঃখের বিষয়, আমরা বিভিন্ন কারণে এটি প্রয়োগ করতে পারি না । আমাদের গ্রহণ করতে হবে যে নিক্ষেপ-পদক্ষেপ একটি সম্ভাবনা।

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

সুতরাং আপনি যদি চান তবে আরও শক্তিশালী গ্যারান্টি সরবরাহ করতে পারেন।

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

vector<int> func()
{
  vector<int> v;
  //fill up `v`.
  return v;
}

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

বেশিরভাগ কোডে, সরানো থেকে অবজেক্টের উদাহরণ ব্যবহারের সম্ভাবনা কম।

শিরোনামে "এই বস্তুটি সরিয়ে নেওয়ার পরে, কেবল এটি নথিভুক্ত করার পক্ষে কি যথেষ্ট?" শিরোনামে এটি ধ্বংস করা ছাড়া এটির সাথে অন্য কিছু করা অবৈধ (ইউবি)?

এটি প্রতিটি পদ্ধতির কলটিতে বৈধ কিনা তা ধারাবাহিকভাবে দাবি করা আরও ভাল?

পূর্বশর্ত থাকার পুরো বিষয়টি হ'ল এই জিনিসগুলি পরীক্ষা করা নয়operator[]পূর্ব শর্ত রয়েছে যে vectorপ্রদত্ত সূচকের সাথে একটি উপাদান রয়েছে। আপনি যদি আকারের বাইরে অ্যাক্সেস করার চেষ্টা করেন তবে আপনি ইউবি পাবেন vectorvector::at নেই যেমন একটি পূর্বশর্ত আছে; এটি স্পষ্টভাবে একটি ব্যতিক্রম ছুঁড়ে দেয় যদি এর vectorকোনও মান না থাকে।

পূর্বশর্তগুলি কার্য সম্পাদনের কারণে বিদ্যমান। এগুলি তাই যাতে আপনাকে কলকারী যাতে তাদের জন্য যাচাই করতে পারে তা পরীক্ষা করতে হবে না। প্রতিটি কলকে খালি v[0]আছে কিনা vতা পরীক্ষা করতে হবে না ; প্রথমটি একটাই করে।

ক্লাসটি অনুলিপিযোগ্য করে তোলা কি ভাল তবে চলমান নয়?

না। আসলে, কোনও শ্রেণি কখনই "অনুলিপিযোগ্য তবে চলনযোগ্য নয়" হওয়া উচিত । যদি এটি অনুলিপি করা যায়, তবে এটি অনুলিপি কনস্ট্রাক্টরকে কল করে স্থানান্তরিত করতে সক্ষম হওয়া উচিত। এটি যদি আপনি কোনও ব্যবহারকারী-সংজ্ঞায়িত অনুলিপি নির্ধারক ঘোষণা করেন তবে সরানো কনস্ট্রাক্টর ঘোষণা না করেন তবে এটি সি ++ 11 এর মানক আচরণ। আপনি যদি বিশেষ পদক্ষেপ শব্দার্থক প্রয়োগ করতে না চান তবে এটি আপনার ব্যবহার করা উচিত।

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


5
খুশী হলাম। +1 টি। আমি লক্ষ করব যে: "পূর্বশর্ত থাকার পুরো বিষয়টি হ'ল এই বিষয়গুলি পরীক্ষা করা নয়।" - আমি মনে করি না যে এটি দৃ as়তার সাথে জড়িত। পূর্ববর্তী শর্তাদি পরীক্ষা করার জন্য উত্সাহগুলি হ'ল আইএমএইচও (যা বেশিরভাগ সময় কমপক্ষে))
মার্টিন বা

3
অনুলিপি / মুভ বিভ্রান্তির বিষয়টি বুঝতে পেরে স্পষ্ট করা যায় যে কোনও মুভ কর্টর নতুন বস্তুর অনুরূপ সহ যে কোনও রাজ্যে উত্স অবজেক্টটি ছেড়ে যেতে পারে - যার অর্থ সম্ভাব্য ফলাফলগুলি একটি অনুলিপি কর্ণধারের সুপারস্টেট।
MSalters
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.