আমি কেন সি ++ এ বিমূর্ত শ্রেণীর জন্য ভার্চুয়াল ডেস্ট্রাক্টর ঘোষণা করব?


165

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

উত্তর:


196

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

উদাহরণ স্বরূপ

class Interface
{
   virtual void doSomething() = 0;
};

class Derived : public Interface
{
   Derived();
   ~Derived() 
   {
      // Do some important cleanup...
   }
};

void myFunc(void)
{
   Interface* p = new Derived();
   // The behaviour of the next line is undefined. It probably 
   // calls Interface::~Interface, not Derived::~Derived
   delete p; 
}

4
delete pঅনির্ধারিত আচরণের আহ্বান জানায়। এটি কল করার নিশ্চয়তা নেই Interface::~Interface
মানকারসে

@ মঙ্কারসে: আপনি কী ব্যাখ্যা করতে পারেন যে এটি কী কারণে অপরিজ্ঞাত রয়েছে? যদি ডেরিভড তার নিজস্ব ডেস্ট্রাক্টর বাস্তবায়ন না করে, তবে এটি কি এখনও অপরিজ্ঞাত আচরণ হবে?
পঙ্কডুডল

14
@Wallacoloo: এটা কারণ undefined হয় [expr.delete]/: ... if the static type of the object to be deleted is different from its dynamic type, ... the static type shall have a virtual destructor or the behavior is undefined. ...। এটি উদ্ঘাটিত হবে যদি ডেরাইভড প্রকটভাবে উত্পন্ন উত্পাদক ব্যবহার করে।
মানকারে

37

আপনার প্রশ্নের উত্তর প্রায়শই হয় তবে সবসময় হয় না। যদি আপনার বিমূর্ত শ্রেণি ক্লায়েন্টদের কাছে এটির পয়েন্টারে কল করতে নিষেধ করে (বা এটি যদি তার ডকুমেন্টেশনে এটি বলে) তবে আপনি ভার্চুয়াল ডেস্ট্রাক্টর ঘোষণা না করতে পারেন।

আপনি ক্লায়েন্টদের এটির বিনষ্টককে সুরক্ষিত করে একটি পয়েন্টারে কল মুছতে নিষেধ করতে পারেন। এর মতো কাজ করা, ভার্চুয়াল ডেস্ট্রাক্টরকে বাদ দেওয়া পুরোপুরি নিরাপদ এবং যুক্তিসঙ্গত।

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

[এই নিবন্ধে আইটেম 4 দেখুন: http://www.gotw.ca/publications/mill18.htm ]


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

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

মিশেল, আমি তাই বলেছি :) "আপনি যদি তা করেন তবে আপনি আপনার ধ্বংসকারীকে সুরক্ষিত করে তোলেন you যদি আপনি এটি করেন, ক্লায়েন্টরা সেই ইন্টারফেসের পয়েন্টার ব্যবহার করে মুছতে সক্ষম হবেন না।" এবং প্রকৃতপক্ষে এটি ক্লায়েন্টদের উপর নির্ভর করে না, তবে ক্লায়েন্টদের "আপনি করতে পারবেন না ..." বলার জন্য এটি প্রয়োগ করতে হবে। আমি কোনও বিপদ দেখছি না
জোহানেস স্কাউব -

আমি এখন আমার উত্তরের দুর্বল শব্দটি স্থির করেছি। এটি এখন স্পষ্টভাবে জানিয়েছে যে এটি ক্লায়েন্টদের উপর নির্ভর করে না। আসলে আমি ভেবেছিলাম এটা স্পষ্ট যে ক্লায়েন্টদের উপর কিছু করা নির্ভর করে যেভাবেই হয়। ধন্যবাদ :)
জোহানেস স্কাউব -

2
সুরক্ষিত ধ্বংসকারীদের উল্লেখ করার জন্য +1, যা বেস ক্লাসে কোনও পয়েন্টার মোছার সময় দুর্ঘটনাক্রমে ভুল ডেস্ট্রাক্টরকে কল করার সমস্যার অন্য "উপায়"।
j_random_hacker

23

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

  1. আপনার ক্লাসটি কি বেস ক্লাস হিসাবে ব্যবহার করার ইচ্ছা রয়েছে?
    • নং: ক্লাসের প্রতিটি বস্তুর উপর ঘোষণা এড়ানোর ভি-পয়েন্টার জনসাধারণের অ ভার্চুয়াল বিনাশকারী *
    • হ্যাঁ: পরের প্রশ্নটি পড়ুন।
  2. আপনার বেস শ্রেণি কি বিমূর্ত? (অর্থাত্ কোনও ভার্চুয়াল বিশুদ্ধ পদ্ধতি?)
    • না: আপনার শ্রেণি শ্রেণিবিন্যাসকে নতুন করে ডিজাইন করে আপনার বেস শ্রেণিকে বিমূর্ত করার চেষ্টা করুন
    • হ্যাঁ: পরের প্রশ্নটি পড়ুন।
  3. আপনি কি বেস পয়েন্টারের মাধ্যমে পলিমারফিক মুছে ফেলার অনুমতি দিতে চান?
    • না: অযাচিত ব্যবহার রোধ করতে সুরক্ষিত ভার্চুয়াল ডেস্ট্রাক্টর ঘোষণা করুন।
    • হ্যাঁ: সর্বজনীন ভার্চুয়াল ডেস্ট্রাক্টর ঘোষণা করুন (এই ক্ষেত্রে কোনও ওভারহেড নেই)।

আশা করি এটা কাজে লাগবে.

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

তথ্যসূত্র:


11
এই উত্তরটি আংশিক পুরানো, সি ++ এ এখন একটি চূড়ান্ত কীওয়ার্ড রয়েছে।
Étienne

10

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

অতএব, আপনি মেমরি ফাঁস হওয়ার সম্ভাবনা খুলছেন

class IFoo
{
  public:
    virtual void DoFoo() = 0;
};

class Bar : public IFoo
{
  char* dooby = NULL;
  public:
    virtual void DoFoo() { dooby = new char[10]; }
    void ~Bar() { delete [] dooby; }
};

IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted

সত্য, প্রকৃতপক্ষে উদাহরণটিতে এটি কেবল মেমরি ফাঁস নয়, সম্ভবত ক্র্যাশও হতে পারে: - /
ইভান তেরান

7

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

উদাহরণস্বরূপ:

Base *p = new Derived;
// use p as you see fit
delete p;

অসুস্থ গঠিত যদি Baseএকটি ভার্চুয়াল বিনাশকারী নেই কারণ এটি বস্তু মুছে ফেলতে যেমন যদি এটি একটি ছিল প্রচেষ্টা করা হবে Base *


আপনি বুস্ট :: শেয়ারড_পয়েন্টার পি (নতুন ডেরাইভড) কে বুস্টের মতো দেখতে ঠিক করতে চান না: শেয়ারড_পয়েন্টার <বেস> পি (নতুন ডেরিভড); ? সম্ভবত
পিপিএল

সম্পাদনা: লিটবের পরামর্শ অনুসারে কোণ বন্ধনীগুলি দৃশ্যমান করতে কয়েকটি কোড "কোডিফাইড" করা হয়েছে।
j_random_hacker

@EvanTeran: আমি নিশ্চিত না যেহেতু উত্তর মূলত পোস্ট করা হয়েছে এই পরিবর্তিত হয়েছে কিনা আছি (এ বুস্ট ডকুমেন্টেশন boost.org/doc/libs/1_52_0/libs/smart_ptr/shared_ptr.htm প্রস্তাব দেওয়া এটা থাকতে পারে), কিন্তু এটা সত্য নয় এই দিনগুলিতে যা shared_ptrঅবজেক্টটি মুছে ফেলার চেষ্টা করবে যেন এটি ছিল Base *- এটি আপনি এটি তৈরি করেছেন এমন জিনিসটির কথা মনে পড়ে। রেফারেন্সযুক্ত লিঙ্কটি দেখুন, বিশেষত সেই বিটটিতে যা বলে যে "ডিস্ট্রাক্টর একই পয়েন্টার দিয়ে মুছতে কল করবে, মূল ধরণের সাথে সম্পূর্ণ হবে, এমনকি টিতে ভার্চুয়াল ডেস্ট্রাক্টর না থাকলেও বা শূন্য থাকে।"
স্টুয়ার্ট গোলোডেটজ

@ স্টুয়ার্টগলডেটজ: হুম, আপনি ঠিক থাকতে পারেন, তবে আমি সত্যই নিশ্চিত নই। ভার্চুয়াল ডেস্ট্রাক্টরের অভাবের কারণে এটি এখনও এই প্রসঙ্গে অসুস্থ হয়ে উঠতে পারে । এটি দেখার জন্য মূল্যবান।
ইভান তেরান

@EvanTeran: - ক্ষেত্রে এটি সহায়ক stackoverflow.com/questions/3899790/shared-ptr-magic
স্টুয়ার্ট গোলোডেটজ

5

এটি কেবল ভাল অনুশীলনই নয়। এটি কোনও শ্রেণি শ্রেণিবিন্যাসের জন্য নিয়ম # 1।

  1. সি ++ এর শ্রেণিবদ্ধের সর্বাধিক শ্রেণীর বেসটিতে একটি ভার্চুয়াল ডেস্ট্রাক্টর থাকতে হবে

এখন কেন। সাধারণ প্রাণীক্রমক্রম নিন। ভার্চুয়াল ডেস্ট্রাক্টররা অন্য কোনও পদ্ধতি কল হিসাবে ভার্চুয়াল প্রেরণের মধ্য দিয়ে যায়। নিচের উদাহরণটি ধরুন।

Animal* pAnimal = GetAnimal();
delete pAnimal;

ধরে নিন যে অ্যানিম্যাল একটি বিমূর্ত শ্রেণি। সি ++ সঠিক কল করতে ডাস্টস্ট্রাক্টরকে জানার একমাত্র উপায় হ'ল ভার্চুয়াল পদ্ধতি প্রেরণের মাধ্যমে। যদি ডেস্ট্রাক্টর ভার্চুয়াল না হয় তবে এটি কেবলমাত্র অ্যানিমাল ডেস্ট্রাক্টরকে কল করবে এবং ডাইরেক্ট ক্লাসে কোনও বস্তু ধ্বংস করবে না।

বেস ক্লাসে ডেস্ট্রাক্টরকে ভার্চুয়াল করার কারণ হ'ল এটি উদ্ভূত শ্রেণীর থেকে পছন্দটিকে সরিয়ে দেয়। তাদের ডেস্ট্রাক্টর ডিফল্টভাবে ভার্চুয়াল হয়ে যায়।


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

@ j_random_hacker এটিকে সুরক্ষিত করে তোলা আপনাকে ভুল অভ্যন্তরীণ
মোছা

1
@ জারেডপার: এটি ঠিক, তবে কমপক্ষে আপনি নিজের কোডে নিজেকে দায়বদ্ধ করতে পারেন - ক্লায়েন্ট কোডটি আপনার কোডটি বিস্ফোরণে না ডেকে আনতে পারে তা নিশ্চিত করা কঠিন বিষয় । (একইভাবে, কোনও ডেটা সদস্যকে ব্যক্তিগত তৈরি করা সেই সদস্যের সাথে
মূup়

@ j_random_hacker, একটি ব্লগ পোস্টের সাথে প্রতিক্রিয়া জানাতে দুঃখিত তবে এটি সত্যিই এই দৃশ্যের সাথে খাপ খায়। ব্লগস.এমএসডিএন
অর্চিভ /

@ জারেডপার: দুর্দান্ত পোস্ট, আমি আপনার সাথে 100% সম্মত, বিশেষত খুচরা কোডে চুক্তি পরীক্ষা করার বিষয়ে। আমি কেবল বোঝাতে চাইছি এমন কিছু মামলা রয়েছে যখন আপনি জানেন যে আপনার ভার্চুয়াল ড্টরের দরকার নেই। উদাহরণ: টেমপ্লেট প্রেরণের জন্য ট্যাগ ক্লাস। তাদের 0 আকার রয়েছে, আপনি কেবল বিশেষীকরণগুলি নির্দেশ করতে উত্তরাধিকার ব্যবহার করেন।
j_random_hacker

3

উত্তরটি সহজ, আপনার এটি ভার্চুয়াল হতে হবে অন্যথায় বেস শ্রেণিটি সম্পূর্ণ পলিমারফিক ক্লাস নয়।

    Base *ptr = new Derived();
    delete ptr; // Here the call order of destructors: first Derived then Base.

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

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