খাঁটি বিমূর্ত শ্রেণি এবং ইন্টারফেসের প্রয়োগ


27

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

স্বাভাবিকভাবেই এটি প্রতিটি প্যারেন্ট ক্লাসের জন্য একটি পয়েন্টার দ্বারা এই শ্রেণীর প্রতিটি উদাহরণের আকারকে পুষিয়ে তোলে।

তবে আমি লক্ষ করেছি যে অনেক সি # ক্লাস এবং স্ট্রাক্টের প্রচুর প্যারেন্ট ইন্টারফেস রয়েছে, যা মূলত খাঁটি বিমূর্ত শ্রেণি। আমি বিস্মিত হই যদি বলার প্রতিটি উদাহরণ Decimal, এটির সমস্ত ইন্টারফেসের 6 টি পয়েন্টার সহ ফুলে যায়।

সুতরাং যদি সি # অন্যভাবে ইন্টারফেসগুলি করে, তবে এটি কীভাবে করে, কমপক্ষে একটি সাধারণ বাস্তবায়নে (আমি বুঝতে পারি যে স্ট্যান্ডার্ড নিজেই এই জাতীয় বাস্তবায়ন সংজ্ঞায়িত করতে পারে না)? এবং ক্লাসে খাঁটি ভার্চুয়াল পিতামাতাদের যুক্ত করার সময় কোনও সি ++ বাস্তবায়নের কী অবজেক্ট সাইজ ব্লাট এড়ানোর উপায় রয়েছে?


1
সি # অবজেক্টগুলিতে সাধারণত প্রচুর পরিমাণে মেটাডেটা সংযুক্ত থাকে, সম্ভবত ভিটিবলগুলি এর তুলনায় এত বড় নয়
max7

আপনি আইডিএল বিচ্ছিন্নকরণের সাথে সংকলিত কোড পরীক্ষা করে শুরু করতে পারেন
সর্বাধিক 630

সি ++ স্থিতিশীলভাবে এর "ইন্টারফেস" এর একটি উল্লেখযোগ্য ভগ্নাংশ করে। তুলনা IComparerকরুনCompare
কালেথ

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

1
@ স্টিফেনএম.ওয়েব যতদূর আমি এই এসও উত্তরটি বুঝতে পেরেছি, ভিটিটিগুলি কেবল ভার্চুয়াল উত্তরাধিকারের সাথে নির্মাণ / ধ্বংসের আদেশ দেওয়ার জন্য ব্যবহৃত হয়। তারা পদ্ধতি প্রেরণে অংশ নেয় না এবং অবজেক্টের কোনও স্থানই সংরক্ষণ করে না। যেহেতু সি ++ আপকাস্টগুলি কার্যকরভাবে বস্তু স্লাইসিং সম্পাদন করে, তাই ভ্যাটেবল পয়েন্টার অন্য কোথাও রাখা সম্ভব নয় তবে বস্তুতে (যা এমআই এর জন্য বস্তুর মাঝখানে vtable পয়েন্টার যুক্ত করে)। g++-7 -fdump-class-hierarchyআউটপুট দেখে আমি যাচাই করেছি ।
আমন

উত্তর:


35

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

যেমন OpenJDK জেভিএম, প্রত্যেক বর্গ সব বাস্তবায়িত ইন্টারফেসের জন্য vtables একটি অ্যারের রয়েছে (একটি ইন্টারফেস vtable একটি বলা হয় itable )। যখন একটি ইন্টারফেস পদ্ধতি বলা হয়, তখন এই ইন্টারফেসটির অক্ষমতার জন্য এই অ্যারেটি রৈখিকভাবে অনুসন্ধান করা হয়, তারপরে সেই পদ্ধতিটি সেই সক্ষমের মাধ্যমে প্রেরণ করা যায়। ক্যাশিং ব্যবহার করা হয় যাতে প্রতিটি কল সাইট পদ্ধতি প্রেরণের ফলাফল মনে রাখে, সুতরাং যখন কংক্রিটের অবজেক্টের ধরণের পরিবর্তন হয় তখন এই অনুসন্ধানটি পুনরাবৃত্তি করতে হয়। পদ্ধতি প্রেরণের জন্য সিউডোকোড:

// Dispatch SomeInterface.method
Method const* resolve_method(
    Object const* instance, Klass const* interface, uint itable_slot) {

  Klass const* klass = instance->klass;

  for (Itable const* itable : klass->itables()) {
    if (itable->klass() == interface)
      return itable[itable_slot];
  }

  throw ...;  // class does not implement required interface
}

(ওপেনজেডকে হটস্পট ইন্টারপ্রেটার বা x86 সংকলকটিতে আসল কোডটি তুলনা করুন ))

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

Method const* resolve_method(
    Object const* instance, Klass const* interface, uint interface_slot) {

  Klass const* klass = instance->klass;

  // Walk all base classes to find slot map
  for (Klass const* base = klass; base != nullptr; base = base->base()) {
    // I think the CLR actually uses hash tables instead of a linear search
    for (SlotMap const* slot_map : base->slot_maps()) {
      if (slot_map->klass() == interface) {
        uint vtable_slot = slot_map[interface_slot];
        return klass->vtable[vtable_slot];
      }
    }
  }

  throw ...;  // class does not implement required interface
}

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

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

তথ্যসূত্র এবং আরও পড়া:

  • উইকিপিডিয়া: ইনলাইন ক্যাচিং । ক্যাচিং পদ্ধতির বিষয়ে আলোচনা করে যা ব্যয়বহুল পদ্ধতির অনুসন্ধান এড়ানোর জন্য ব্যবহার করা যেতে পারে। সাধারণত vtable- ভিত্তিক প্রেরণের জন্য প্রয়োজন হয় না, তবে উপরের ইন্টারফেস প্রেরণের কৌশলগুলির মতো আরও ব্যয়বহুল প্রেরণ প্রক্রিয়ার জন্য খুব আকাঙ্ক্ষিত।
  • ওপেনজেডকে উইকি (২০১৩): ইন্টারফেস কল । এটি সম্পর্কে আলোচনা করে।
  • পোবার, নিউয়ার্ড (২০০৯): এসএসসিএলআই ২.০ অভ্যন্তরীণ। বইয়ের ৫ ম অধ্যায়ে স্লট মানচিত্রের বিষয়ে বিস্তারিত আলোচনা করা হয়েছে। কখনও প্রকাশিত হয়নি তবে তাদের ব্লগে লেখকগণ উপলব্ধ করেছেনপিডিএফ লিংক থেকে সরানো হয়েছে। এই বইটি সম্ভবত আর সিএলআরের বর্তমান অবস্থা প্রতিফলিত করে না।
  • কোরসিএলআর (2006): ভার্চুয়াল স্টাব প্রেরণ । ইন: বুক অফ দ্য রানটাইম ব্যয়বহুল চেহারাগুলি এড়াতে স্লট মানচিত্র এবং ক্যাশে নিয়ে আলোচনা করে।
  • কেনেডি, সিম (2001): .NET প্রচলিত ভাষা রানটাইমটির জন্য জেনেরিক্সের নকশা এবং বাস্তবায়ন । ( পিডিএফ লিঙ্ক )। জেনেরিকগুলি প্রয়োগ করতে বিভিন্ন পন্থা আলোচনা করে। জেনেরিক্স পদ্ধতি প্রেরণের সাথে ইন্টারঅ্যাক্ট করে কারণ পদ্ধতিগুলি বিশেষায়িত হতে পারে তাই ভ্যাটবিলগুলি আবারও লিখতে হতে পারে।

জাভা এবং সিএলআর উভয় কীভাবে এটি অর্জন করতে পারে সে সম্পর্কে অতিরিক্ত বিবরণের প্রত্যাশায় @amon দুর্দান্ত উত্তরের জন্য ধন্যবাদ!
ক্লিনটন

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

1
callvirtCEE_CALLVIRTকোরএলসিআর- এ একেএ হ'ল সিআইএল নির্দেশ যা কলিং ইন্টারফেসের পদ্ধতিগুলি পরিচালনা করে, যদি কেউ রানটাইম কীভাবে এই সেটআপটি পরিচালনা করে সে সম্পর্কে আরও পড়তে চায়।
jrh

মনে রাখবেন যে, callopcode জন্য ব্যবহার করা হয় static, পদ্ধতি মজার callvirtব্যবহার করা হয় ক্লাস হয় এমনকি যদি sealed
jrh

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

2

স্বাভাবিকভাবেই এটি প্রতিটি প্যারেন্ট ক্লাসের জন্য একটি পয়েন্টার দ্বারা এই শ্রেণীর প্রতিটি উদাহরণের আকারকে পুষিয়ে তোলে।

যদি 'প্যারেন্ট ক্লাস' বলতে আপনার বোঝায় 'বেস ক্লাস' তবে জিসিসিতে এটি হয় না (আমি অন্য কোনও সংকলকও আশা করি না)।

সি এর বি থেকে উদ্ভূত ক এর ক্ষেত্রে A এর বহুভুজ বর্গ যেখানে সি এর উদাহরণটি ঠিক একটি vtable থাকবে have

সংকলকটির এ এর ​​ভিটেবলের ডেটা বি এবং বি এর সিগুলিতে মার্জ করার জন্য প্রয়োজনীয় সমস্ত তথ্য রয়েছে।

এখানে একটি উদাহরণ: https://godbolt.org/g/sfdtNh

আপনি দেখতে পাবেন যে কোনও ভিটেবলের কেবলমাত্র একটি সূচনা আছে।

আমি টীকা দিয়ে এখানে মূল ফাংশনটির জন্য সমাবেশের আউটপুট অনুলিপি করেছি:

main:
        push    rbx

# allocate space for a C on the stack
        sub     rsp, 16

# initialise c's vtable (note: only one)
        mov     QWORD PTR [rsp+8], OFFSET FLAT:vtable for C+16

# use c    
        lea     rdi, [rsp+8]
        call    do_something(C&)

# destruction sequence through virtual destructor
        mov     QWORD PTR [rsp+8], OFFSET FLAT:vtable for B+16
        lea     rdi, [rsp+8]
        call    A::~A() [base object destructor]

        add     rsp, 16
        xor     eax, eax
        pop     rbx
        ret
        mov     rbx, rax
        jmp     .L10

রেফারেন্সের জন্য সম্পূর্ণ উত্স:

struct A
{
    virtual void foo() = 0;
    virtual ~A();
};

struct B : A {};

struct C : B {

    virtual void extrafoo()
    {
    }

    void foo() override {
        extrafoo();
    }

};

int main()
{
    extern void do_something(C&);
    auto c = C();
    do_something(c);
}

আমরা যদি একটি নিতে উদাহরণ যেখানে উপশ্রেণী দুই বেস ক্লাস থেকে সরাসরি উত্তরাধিকারী পছন্দ class Derived : public FirstBase, public SecondBaseতারপর দুই vtables হতে পারে। আপনি g++ -fdump-class-hierarchyক্লাস বিন্যাসটি দেখতে দৌড়াতে পারেন (আমার লিঙ্কযুক্ত ব্লগ পোস্টেও দেখানো হয়েছে)। এরপরে গডবোল্ট ২ য় ভেটেবল নির্বাচন করার জন্য কলের আগে অতিরিক্ত পয়েন্টার বৃদ্ধি দেখায় ।
আমন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.