c++ - معنى - ما هو تشريح الكائن؟




معنى كلمة عنوان بالانجليزي (12)

ذكرها شخص ما في IRC ، ولكن ليس لدى google إجابة جيدة.


"Slicing" هو المكان الذي تقوم فيه بتعيين كائن من فئة مشتقة إلى مثيل لفئة أساسية ، وبالتالي فقد جزء من المعلومات - بعضها "مقسم" بعيدًا.

فمثلا،

class A {
   int foo;
};

class B : public A {
   int bar;
};

لذلك ، يحتوي كائن من النوع B على اثنين من أعضاء البيانات ، foo و bar .

ثم إذا كنت تكتب هذا:

B b;

A a = b;

ثم يتم فقدان المعلومات الموجودة في b bar الأعضاء في.


أنا فقط ركض عبر مشكلة تشريح وهبط على الفور هنا. لذا اسمحوا لي أن أضيف سنتي إلى هذا.

دعنا نحصل على مثال من "كود الإنتاج" (أو شيء يأتي قريبًا):

لنفترض أن لدينا شيئًا ينقل الأفعال. واجهة مستخدم في مركز التحكم على سبيل المثال.
تحتاج واجهة المستخدم هذه إلى الحصول على قائمة بالأشياء التي يمكن إرسالها حاليًا. لذلك نحدد فئة تحتوي على معلومات الإرسال. دعونا نسميها Action . لذلك فإن Action يحتوي على بعض المتغيرات الخاصة بالأعضاء. للبساطة لدينا 2 فقط ، كونها std::string name و std::function<void()> f . ثم أن لديها void activate() الذي ينفذ العضو f .

بحيث يحصل على واجهة المستخدم std::vector<Action> الموفر. تخيل بعض الوظائف مثل:

void push_back(Action toAdd);

الآن أنشأنا كيف يبدو من منظور واجهة المستخدم. لا مشكلة حتى الآن. لكن بعض الأشخاص الآخرين الذين يعملون في هذا المشروع يقررون فجأة أن هناك إجراءات متخصصة تحتاج إلى مزيد من المعلومات في كائن Action . لأي سبب على الإطلاق. ويمكن أيضا أن تحل مع التقاط لامدا. لا يتم أخذ هذا المثال 1-1 من الشفرة.

لذلك يستمد الرجل من Action لإضافة نكهة خاصة به.
هو يمرّ على سبيل المثال من فصله المخمّر في المنزل إلى push_back لكن بعد ذلك البرنامج يخترق.

اذا ماذا حصل؟
كما كنت قد خمنت: تم تقسيم الكائن.

تم فقد المعلومات الإضافية من المثيل ، و f الآن عرضة لسلوك غير معروف.

آمل أن يجلب هذا المثال الضوء لأولئك الأشخاص الذين لا يستطيعون تخيل الأشياء عندما يتحدثون عن A s و B s مشتقة بطريقة ما.


اعثر على إجابات مماثلة هنا: http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html

يعني التشريح أنه يتم تجاهل البيانات التي يتم إضافتها بواسطة فئة فرعية عندما يتم تمرير كائن من الفئة الفرعية أو إعادته بقيمة أو من دالة تتوقع كائن فئة أساسي.

Explanation: خذ بعين الاعتبار تعريف الفصل التالي:

           class baseclass
          {
                 ...
                 baseclass & operator =(const baseclass&);
                 baseclass(const baseclass&);
          }
          void function( )
          {
                baseclass obj1=m;
                obj1=m;
          }

كما لا تعرف وظائف نسخ baseclass أي شيء عن مشتق فقط يتم نسخ الجزء الأساسي من المشتق. يشار إلى هذا عادةً بالشريحة.


المطابقة الثالثة في جوجل لـ "C ++ slicing" يعطيني هذه المقالة في ويكيبيديا http://en.wikipedia.org/wiki/Object_slicing وهذا (تسخين ، لكن الوظائف القليلة الأولى تحدد المشكلة): http://bytes.com/forum/thread163565.html

لذلك عندما تقوم بتعيين كائن من فئة فرعية إلى الطبقة الفائقة. لا تعرف الطبقة المتفوقة أي شيء عن المعلومات الإضافية في الفئة الفرعية ، ولا تتوفر مساحة لتخزينها ، لذا يتم "فصل" المعلومات الإضافية.

إذا كانت هذه الروابط لا تعطي معلومات كافية للحصول على "إجابة جيدة" ، فالرجاء تعديل سؤالك ليخبرنا بما تبحث عنه.


حسنًا ، سأجربها بعد قراءة العديد من المشاركات التي تشرح تشريح الكائنات ولكن ليس كيف تصبح مشكلة.

السيناريو الشرط الذي يمكن أن يؤدي إلى تلف في الذاكرة هو ما يلي:

  • يوفر الصف (عن طريق الخطأ ، وربما المحول مترجم) تعيين على فئة قاعدة متعددة الأشكال.
  • نسخ العميل وشرائح مثيل لفئة مشتقة.
  • يقوم العميل باستدعاء وظيفة عضو ظاهرية تصل إلى حالة مقطوعة.

عندما يتم تعيين كائن فئة Derived إلى كائن فئة أساسي ، يتم نسخ كافة أعضاء كائن فئة مشتقة إلى كائن فئة أساسي فيما عدا الأعضاء غير الموجودين في الفئة الأساسية. يتم تقسيم هؤلاء الأعضاء بعيدا عن طريق المجمع. يسمى هذا "تقسيم كائن".

هنا مثال:

#include<bits/stdc++.h>
using namespace std;
class Base
{
    public:
        int a;
        int b;
        int c;
        Base()
        {
            a=10;
            b=20;
            c=30;
        }
};
class Derived : public Base
{
    public:
        int d;
        int e;
        Derived()
        {
            d=40;
            e=50;
        }
};
int main()
{
    Derived d;
    cout<<d.a<<"\n";
    cout<<d.b<<"\n";
    cout<<d.c<<"\n";
    cout<<d.d<<"\n";
    cout<<d.e<<"\n";


    Base b = d;
    cout<<b.a<<"\n";
    cout<<b.b<<"\n";
    cout<<b.c<<"\n";
    cout<<b.d<<"\n";
    cout<<b.e<<"\n";
    return 0;
}

سوف تولد:

[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'

في C ++ ، يمكن تعيين كائن فئة مشتقة إلى كائن فئة أساسية ولكن الطريقة الأخرى غير ممكنة.

class Base { int x, y; };

class Derived : public Base { int z, w; };

int main() 
{
    Derived d;
    Base b = d; // Object Slicing,  z and w of d are sliced off
}

يحدث تقطيع كائن عند تعيين كائن فئة مشتقة إلى كائن فئة أساسية ، يتم قطع السمات الإضافية لكائن فئة مشتقة لتشكيل كائن الفئة الأساسي.


لذلك ... لماذا يفقد المعلومات المشتقة سيئة؟ ... لأن مؤلف الفئة المشتقة قد قام بتغيير التمثيل بحيث يؤدي تشريح المعلومات الإضافية إلى تغيير القيمة التي يتم تمثيلها بواسطة الكائن. يمكن أن يحدث هذا إذا تم استخدام الفئة المشتقة لتخزين نسخة مخبأة أكثر فاعلية لعمليات معينة ، ولكنها مكلفة لتحويلها إلى التمثيل الأساسي.

واعتقد أيضا أن أحد الأشخاص يجب أن يذكر أيضًا ما يجب عليك فعله لتجنب التقسيم ... احصل على نسخة من معايير C ++ Coding ، و 101 قواعد إرشادية للقواعد ، وأفضل الممارسات. التعامل مع التقسيم هو # 54.

يقترح نمطًا متطورًا إلى حد ما للتعامل تمامًا مع المشكلة: امتلاك مُنشئ نسخة محمية ، DoClone ظاهري محمي خالص ، واستنساخ عام مع تأكيد سيخبرك ما إذا كانت فئة مشتقة (إضافية) فشلت في تنفيذ DoClone بشكل صحيح. (إن طريقة النسخ تجعل نسخة عميقة مناسبة من الكائن متعدد الأشكال).

يمكنك أيضًا وضع علامة على مُنشئ النسخ على القاعدة الأساسية التي تسمح بالتقطيع الواضح إذا كان ذلك مطلوبًا.


معظم الإجابات هنا تفشل في شرح المشكلة الحقيقية للتقطيع. يشرحون فقط الحالات الحميدة للتقطيع ، وليس الحالات الخادعة. افترض ، مثل الإجابات الأخرى ، أنك تتعامل مع فئتين A و B ، حيث تستمد B (علنًا) من A

في هذه الحالة ، يتيح لك C ++ تمرير مثيل من عامل التعيين من B إلى A (وأيضاً إلى مُنشئ النسخ). وينجح هذا الأمر لأنه يمكن تحويل مثيل B إلى const A& ، وهو ما تتوقعه مشغلات المهمة ومنشئو النسخ.

حالة حميدة

B b;
A a = b;

لا شيء سيء يحدث هناك - لقد طلبت مثال A الذي هو نسخة من B ، وهذا هو بالضبط ما تحصل عليه. بالتأكيد ، لن يحتوي على بعض أعضاء b ، ولكن كيف يجب أن يكون ذلك؟ إنه A ، بعد كل شيء ، ليس B ، لذلك لم يسمع حتى عن هؤلاء الأعضاء ، ناهيك عن القدرة على تخزينها.

القضية الغادرة

B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!

قد تعتقد أن b2 سيكون نسخة من b1 بعد ذلك. لكن ، للأسف ، ليس كذلك ! إذا قمت بفحصها ، فسوف تكتشف أن b2 هو مخلوق Frankensteinian ، مصنوع من بعض أجزاء b1 (القطع التي يرثها B من A ) ، وبعض أجزاء b2 (القطع التي تحتوي على B فقط). أوتش!

ماذا حدث؟ حسنا ، C ++ افتراضيا لا يعامل مشغلي الاحالة كما virtual . وبالتالي ، فإن الخط a_ref = b1 سوف يستدعي مشغل التعيين A ، وليس العامل B ويرجع ذلك إلى أنه بالنسبة للوظائف غير الظاهرية ، يحدد النوع الذي تم تحديده (والذي هو A& ) الوظيفة التي يتم استدعاؤها ، على عكس النوع الفعلي (الذي سيكون B ، حيث يشير a_ref إلى مثيل B ). الآن ، مشغّل المهمة A لا يعرف فقط عن الأعضاء المعلن عنها في A ، لذا سوف ينسخ فقط تلك ، تاركاً الأعضاء المضافين في B تغيير.

حل

عادةً ما يكون تخصيص أجزاء فقط من كائن غير منطقي ، ومع ذلك فإن C ++ لا توفر للأسف طريقة مضمنة لمنع ذلك. يمكنك ، مع ذلك ، لف الخاص بك. الخطوة الأولى هي جعل مشغل التعيين افتراضيًا . سيضمن ذلك دائمًا عامل التعيين الخاص بالنوع الفعلي والذي يتم استدعاؤه ، وليس النوع المعلن . الخطوة الثانية هي استخدام dynamic_cast للتحقق من أن الكائن المعين له نوع متوافق. الخطوة الثالثة هي القيام بالواجب الفعلي في عضو (محمي)! assign() ، لأن B (s assign() B ربما تريد استخدام A 's assign() أعضاء A

class A {
public:
  virtual A& operator= (const A& a) {
    assign(a);
    return *this;
  }

protected:
  void assign(const A& a) {
    // copy members of A from a to this
  }
};

class B : public A {
public:
  virtual B& operator= (const A& a) {
    if (const B* b = dynamic_cast<const B*>(&a))
      assign(*b);
    else
      throw bad_assignment();
    return *this;
  }

protected:
  void assign(const B& b) {
    A::assign(b); // Let A's assign() copy members of A from b to this
    // copy members of B from b to this
  }
};

لاحظ أنه من أجل راحة خالصة ، يتجاوز operator= B ' operator= coffariantly نوع الإرجاع ، لأنه يعرف أنه يعيد مثال B


هذه كلها إجابات جيدة. أود فقط إضافة مثال تنفيذ عند تمرير الكائنات حسب القيمة مقابل كمرجع:

#include <iostream>

using namespace std;

// Base class
class A {
public:
    A() {}
    A(const A& a) {
        cout << "'A' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am an 'A'" << endl; }
};

// Derived class
class B: public A {
public:
    B():A() {}
    B(const B& a):A(a) {
        cout << "'B' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am a 'B'" << endl; }
};

void g(const A & a) {
    a.run();
}

void h(const A a) {
    a.run();
}

int main() {
    cout << "Call by reference" << endl;
    g(B());
    cout << endl << "Call by copy" << endl;
    h(B());
}

الناتج هو:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'

1. تعريف مشكلة التسلسل

إذا كانت D فئة مشتقة من الفئة B الأساسية ، فيمكنك تعيين كائن من النوع Derived إلى متغير (أو معلمة) من النوع Base.

مثال

class Pet
{
 public:
    string name;
};
class Dog : public Pet
{
public:
    string breed;
};

int main()
{   
    Dog dog;
    Pet pet;

    dog.name = "Tommy";
    dog.breed = "Kangal Dog";
    pet = dog;
    cout << pet.breed; //ERROR

على الرغم من السماح بالتعيين المذكور أعلاه ، فإن القيمة التي يتم تعيينها للحيوانات الأليفة المتغيرة تفقد حقل السلالة. وهذا ما يسمى مشكلة تشريح .

2. كيفية إصلاح مشكلة التسلسل

لدحر المشكلة ، نستخدم المؤشرات للمتغيرات الديناميكية.

مثال

Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;         
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed; 

في هذه الحالة ، لن يتم فقدان أي من أعضاء البيانات أو دالات الأعضاء للمتغير الديناميكي الذي يتم الإشارة إليه بواسطة ptrD (كائن فئة تابع). بالإضافة إلى ذلك ، إذا كنت بحاجة إلى استخدام الدالات ، يجب أن تكون الوظيفة دالة ظاهرية.


class A 
{ 
    int x; 
};  

class B 
{ 
    B( ) : x(1), c('a') { } 
    int x; 
    char c; 
};  

int main( ) 
{ 
    A a; 
    B b; 
    a = b;     // b.c == 'a' is "sliced" off
    return 0; 
}




object-slicing