ভার্চুয়াল উত্তরাধিকার কীভাবে "হীরা" (একাধিক উত্তরাধিকার) অস্পষ্টতা সমাধান করে?


97
class A                     { public: void eat(){ cout<<"A";} }; 
class B: virtual public A   { public: void eat(){ cout<<"B";} }; 
class C: virtual public A   { public: void eat(){ cout<<"C";} }; 
class D: public         B,C { public: void eat(){ cout<<"D";} }; 

int main(){ 
    A *a = new D(); 
    a->eat(); 
} 

আমি হীরা সমস্যাটি বুঝতে পেরেছি এবং উপরের কোডের সমস্যাটি নেই।

ভার্চুয়াল উত্তরাধিকার কীভাবে সমস্যার সমাধান করে?

আমি যা বুঝি: যখন আমি বলি A *a = new D();, সংকলকটি জানতে চায় যে কোনও ধরণের কোনও বিষয় টাইপের বিন্দুতে Dনির্ধারিত করা যেতে পারে Aতবে এর দুটি পথ রয়েছে যা এটি অনুসরণ করতে পারে, তবে নিজে সিদ্ধান্ত নিতে পারে না।

সুতরাং, ভার্চুয়াল উত্তরাধিকার কীভাবে সমস্যাটি সমাধান করবে (সংকলক সিদ্ধান্ত নিতে সহায়তা করবে)?

উত্তর:


112

আপনি চান: (ভার্চুয়াল উত্তরাধিকারের সাথে অর্জনযোগ্য)

  A  
 / \  
B   C  
 \ /  
  D 

এবং না: (ভার্চুয়াল উত্তরাধিকার ছাড়া কী হয়)

A   A  
|   |
B   C  
 \ /  
  D 

ভার্চুয়াল উত্তরাধিকারের অর্থ হ'ল বেস Aক্লাস 2 নয় কেবলমাত্র 1 টি উদাহরণ থাকবে ।

আপনার ধরণের D2 টি ভ্যাটেবল পয়েন্টার থাকবে (আপনি এগুলি প্রথম চিত্রগুলিতে দেখতে পারেন), একটির জন্য Bএবং Cকার্যত উত্তরাধিকার সূত্রে প্রাপ্ত একজনের জন্য একটি ADএর বস্তুর আকার বৃদ্ধি পেয়েছে কারণ এটি এখন 2 পয়েন্টার সঞ্চয় করে; তবে Aএখন কেবল একজনই আছে।

সুতরাং B::Aএবং C::Aএকই এবং তাই কোনও দ্বিপাক্ষিক কল আসতে পারে না D। আপনি যদি ভার্চুয়াল উত্তরাধিকার ব্যবহার না করেন তবে উপরের দ্বিতীয় চিত্রটি আপনার রয়েছে। এবং এ এর ​​সদস্যদের যে কোনও কল তারপরেই দ্বিধাবিভক্ত হয়ে যায় এবং আপনি কোন পথটি গ্রহণ করতে চান তা নির্দিষ্ট করতে হবে।

উইকিপিডিয়ায় এখানে আরও একটি ভাল রুরডাউন এবং উদাহরণ রয়েছে


4
Vtable পয়েন্টার একটি বাস্তবায়ন বিশদ। সমস্ত সংকলক এই ক্ষেত্রে vtable পয়েন্টার প্রবর্তন করবে না।
কৌতূহলী

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

পরিবর্তে এর ব্যবহার Bবা Cপ্রয়োগের জন্য আমি তার কোডটি কীভাবে সংশোধন করতে পারি ? ধন্যবাদ!
মিন্হ Nghĩa

46

অন্য উত্তর কেন?

ঠিক আছে, এসও এবং বাইরের নিবন্ধগুলিতে অনেকগুলি পোস্টে বলা হয়েছে যে হীরক সমস্যাটি Aদু'জনের পরিবর্তে (প্রতিটি পিতামাতার জন্য একজন D) একক উদাহরণ তৈরি করে এইভাবে অস্পষ্টতার সমাধান করে is যাইহোক, এটি আমাকে প্রক্রিয়াটির ব্যাপক বোঝাপড়া দেয় নি, আমি আরও অনেক প্রশ্নের মতো শেষ করেছি

  1. কী যদি Bএবং Cউদাহরণস্বরূপ Aবিভিন্ন পরামিতি ( D::D(int x, int y): C(x), B(y) {}) দিয়ে প্যারামেট্রাইজড কনস্ট্রাক্টরকে কল করার বিভিন্ন উদাহরণ তৈরি করার চেষ্টা করে ? কোন উদাহরণটি Aঅংশ হিসাবে নির্বাচিত হবে D?
  2. যদি আমি অ-ভার্চুয়াল উত্তরাধিকারের জন্য ব্যবহার করি Bতবে ভার্চুয়ালটির জন্য C? এটি একক উদাহরণস্বরূপ তৈরি জন্য যথেষ্ট Aমধ্যে D?
  3. প্রতিরোধমূলক ব্যবস্থা হিসাবে এখন থেকে আমি কি সর্বদা ভার্চুয়াল উত্তরাধিকারকে প্রতিরোধমূলক ব্যবস্থা হিসাবে ব্যবহার করব যেহেতু এটি ছোটখাটো পারফরম্যান্স ব্যয় এবং অন্য কোনও ত্রুটিগুলি নিয়ে হীরা সমস্যাটি সমাধান করে?

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

ডাবল এ

প্রথমে ভার্চুয়াল উত্তরাধিকার ছাড়াই এই কোডটি দিয়ে শুরু করা যাক:

#include<iostream>
using namespace std;
class A {
public:
    A()                { cout << "A::A() "; }
    A(int x) : m_x(x)  { cout << "A::A(" << x << ") "; }
    int getX() const   { return m_x; }
private:
    int m_x = 42;
};

class B : public A {
public:
    B(int x):A(x)   { cout << "B::B(" << x << ") "; }
};

class C : public A {
public:
    C(int x):A(x) { cout << "C::C(" << x << ") "; }
};

class D : public C, public B  {
public:
    D(int x, int y): C(x), B(y)   {
        cout << "D::D(" << x << ", " << y << ") "; }
};

int main()  {
    cout << "Create b(2): " << endl;
    B b(2); cout << endl << endl;

    cout << "Create c(3): " << endl;
    C c(3); cout << endl << endl;

    cout << "Create d(2,3): " << endl;
    D d(2, 3); cout << endl << endl;

    // error: request for member 'getX' is ambiguous
    //cout << "d.getX() = " << d.getX() << endl;

    // error: 'A' is an ambiguous base of 'D'
    //cout << "d.A::getX() = " << d.A::getX() << endl;

    cout << "d.B::getX() = " << d.B::getX() << endl;
    cout << "d.C::getX() = " << d.C::getX() << endl;
}

আউটপুট মাধ্যমে যেতে দেয়। এক্সিকিউটিং প্রত্যাশিত হিসাবে B b(2);তৈরি করে :A(2)C c(3);

Create b(2): 
A::A(2) B::B(2) 

Create c(3): 
A::A(3) C::C(3) 

D d(2, 3);উভয়ের প্রয়োজন Bএবং Cতাদের প্রত্যেকের নিজস্ব তৈরি করা A, সুতরাং আমাদের দ্বিগুণ Aহয়েছে d:

Create d(2,3): 
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3) 

যে জন্য কারণ d.getX()কম্পাইলার চয়ন করতে পারবে না যা সংকলন ত্রুটি কারণ Aউদাহরণস্বরূপ এটা জন্য পদ্ধতি কল করা উচিত। তবুও নির্বাচিত অভিভাবক শ্রেণীর জন্য সরাসরি পদ্ধতিগুলি কল করা সম্ভব:

d.B::getX() = 3
d.C::getX() = 2

ভার্চুয়ালটি

এখন আসুন ভার্চুয়াল উত্তরাধিকার যোগ করুন। নিম্নলিখিত পরিবর্তনগুলির সাথে একই কোড নমুনা ব্যবহার:

class B : virtual public A
...
class C : virtual public A
...
cout << "d.getX() = " << d.getX() << endl; //uncommented
cout << "d.A::getX() = " << d.A::getX() << endl; //uncommented
...

তৈরি করতে লাফ দিন d:

Create d(2,3): 
A::A() C::C(2) B::B(3) D::D(2, 3) 

আপনি দেখতে পাচ্ছেন, Aডিফল্ট কনস্ট্রাক্টর Bএবং এর কনস্ট্রাক্টরগুলি থেকে পাস হওয়া পরামিতি উপেক্ষা করে তৈরি করা হয়েছে C। অস্পষ্টতা চলে যাওয়ার সাথে সাথে সমস্ত কল getX()একই মান ফেরত দেবে:

d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42

তবে আমরা যদি প্যারামেট্রাইজড কনস্ট্রাক্টরের জন্য কল করতে চাই A? এটি নির্মাণকারীর কাছ থেকে স্পষ্টভাবে কল করে এটি করা যেতে পারে D:

D(int x, int y, int z): A(x), C(y), B(z)

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

কোডের class B: virtual Aঅর্থ, উত্তরাধিকার সূত্রে প্রাপ্ত যে কোনও শ্রেণি Bএখন Aনিজেই তৈরি করার জন্য দায়বদ্ধ , যেহেতু Bএটি স্বয়ংক্রিয়ভাবে তা করবে না।

এই বিবৃতিটি মাথায় রেখে আমার কাছে থাকা সমস্ত প্রশ্নের উত্তর দেওয়া সহজ:

  1. Dসৃষ্টির সময় এবং Bনা Cপ্যারামিটারগুলির জন্য দায়ী A, এটি Dকেবলমাত্র সম্পূর্ণরূপে up
  2. Cএতে তৈরি Aকরার প্রতিনিধিত্ব করবে D, কিন্তু ডায়মন্ডের সমস্যাটি ফিরিয়ে আনার Bনিজস্ব ঘটনা তৈরি করবেA
  3. সরাসরি সন্তানের পরিবর্তে নাতি-নাতনী শ্রেণিতে বেস শ্রেণির পরামিতিগুলি সংজ্ঞায়িত করা ভাল অনুশীলন নয়, তাই যখন হীরকের সমস্যা উপস্থিত থাকে তখন এটি সহ্য করা উচিত এবং এই ব্যবস্থাটি অনিবার্য।

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

45

উদ্ভূত শ্রেণীর উদাহরণগুলি তাদের বেস শ্রেণীর সদস্যদের সঞ্চয় করে ।

ভার্চুয়াল উত্তরাধিকার ব্যতীত, মেমরি বিন্যাসগুলি দেখতে দেখতে ( শ্রেণীর সদস্যদের দুটি কপি নোট করুন ):AD

class A: [A members]
class B: public A [A members|B members]
class C: public A [A members|C members]
class D: public B, public C [A members|B members|A members|C members|D members]

ভার্চুয়াল উত্তরাধিকার সহ, মেমরি বিন্যাসগুলি দেখতে দেখতে ( শ্রেণীর সদস্যদের একক অনুলিপি নোট করুন ):AD

class A: [A members]
class B: virtual public A [B members|A members]
                           |         ^
                           v         |
                         virtual table B

class C: virtual public A [C members|A members]
                           |         ^
                           v         |
                         virtual table C

class D: public B, public C [B members|C members|D members|A members]
                             |         |                   ^
                             v         v                   |
                           virtual table D ----------------|

প্রতিটি উত্পন্ন ক্লাসের জন্য, সংকলক ডেরিভড ক্লাসে সঞ্চিত তার ভার্চুয়াল বেস ক্লাসের সদস্যদের একটি ভার্চুয়াল টেবিল হোল্ডিং পয়েন্টার তৈরি করে এবং উত্পন্ন শ্রেণীর মধ্যে ভার্চুয়াল টেবিলটিতে একটি পয়েন্টার যুক্ত করে।



10

সমস্যাটি সেই পথ নয় যা সংকলককে অনুসরণ করতে হবে। সমস্যা সেই পথের শেষ পয়েন্ট : the ালাইয়ের ফলাফল। রূপান্তরগুলি টাইপ করার সময়, পথটি কিছু যায় আসে না, কেবল চূড়ান্ত ফলাফল হয়।

আপনি যদি সাধারণ উত্তরাধিকার ব্যবহার করেন তবে প্রতিটি পাথের নিজস্ব স্বাতন্ত্র্যসূচক সমাপ্তি রয়েছে, যার অর্থ theালাইয়ের ফলাফল অস্পষ্ট, যা সমস্যা।

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


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

8

আসলে উদাহরণটি নিম্নরূপ হওয়া উচিত:

#include <iostream>

//THE DIAMOND PROBLEM SOLVED!!!
class A                     { public: virtual ~A(){ } virtual void eat(){ std::cout<<"EAT=>A";} }; 
class B: virtual public A   { public: virtual ~B(){ } virtual void eat(){ std::cout<<"EAT=>B";} }; 
class C: virtual public A   { public: virtual ~C(){ } virtual void eat(){ std::cout<<"EAT=>C";} }; 
class D: public         B,C { public: virtual ~D(){ } virtual void eat(){ std::cout<<"EAT=>D";} }; 

int main(int argc, char ** argv){
    A *a = new D(); 
    a->eat(); 
    delete a;
}

... এইভাবে আউটপুটটি সঠিক হবে: "EAT => ডি"

ভার্চুয়াল উত্তরাধিকার কেবল দাদার নকল সমাধান করে! তবে পদ্ধতিগুলি সঠিকভাবে ওভাররাইড করার জন্য আপনার এখনও ভার্চুয়াল হওয়ার জন্য পদ্ধতিগুলি নির্দিষ্ট করতে হবে ...

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