لماذا لا يمكنني استدعاء مُنشئ صفي من مثيل لتلك الفئة في C ++؟

StackOverflow https://stackoverflow.com/questions/2146901

  •  23-09-2019
  •  | 
  •  

سؤال

متى يمكن لموضوع فئة أن يطلق على المدمرة لتلك الفئة ، كما لو كانت وظيفة منتظمة؟ لماذا لا تستطيع استدعاء مُنشئ الفئة نفسها ، كواحدة من وظائفها العادية؟ لماذا يمنعنا المترجم من القيام بذلك؟

علي سبيل المثال:

class c
{
public:
   void add() ;
   c();
   ~c() ;
};

void main()
{
 c objC  ;
 objC.add() ;
 objC.~c() ; // this line compiles
 objC.c() ;  // compilation error
}
هل كانت مفيدة؟

المحلول

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

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

لذلك: لا يوجد استخدام صحيح لمكالمات المنشئ الصريحة ، لذلك لا يُسمح بها. هناك استخدام صحيح لمكالمات Drestructor الصريحة ، لذلك يُسمح (ببناء) ، مع القاعدة التي يجب عليك استخدامها فقط على كائنات لن يتم تدميرها بطريقة أخرى ، أي كائنات تم إنشاؤها باستخدام "وضع جديد" ، وفي ذلك القضية اتصل بهم مرة واحدة بالضبط. إن استخدامها بأي طريقة أخرى ، مثل العديد من أخطاء C ++ ، سيجمع ولكن إعطاء سلوك غير محدد.

نصائح أخرى

أعتقد أنه يمكنك استدعاء Destructor صراحة إذا كنت تتأكد

class c
{
public:
   void add() ;
   c();
   ~c() ;
};

int main()
{
 c objC  ;
 objC.add() ;
 objC.~c() ; // this line compiles
 new (&objC) c;  // placement new invokes constructor for the given memory region
}

لم أر هذا أبدًا في الممارسة العملية ، ولكن من المنطقي أن يعمل (ما لم يتمكن منشئ C ، وفي هذه الحالة ، أتصور ، أن الجحيم قد ينفجر أثناء الاسترخاء المكدس).

ومع ذلك ، فإن ما تريده هو مجرد مهمة:

objC = c();

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

يجب استدعاء المدمر على مثيل موجود للفصل - تدمير الحالة هو ما يفعله. يقوم المنشئ بإنشاء مثيل جديد تمامًا لفئة ، لذا فإن استدعاء مثيل موجود لا معنى له.

هذا مشابه للطريقة الجديدة وحذف العمل:

int * p = new int;    // call to new needs no existing instance
delete p;             // call to delete requires existing instance

ولاحظ في الكود الخاص بك ، سيتم تدمير الكائن مرتين ، مرة واحدة بشكل صريح ، وبمجرد ضمني في نهاية نطاقه المرفق. عادةً ما تقوم فقط باستدعاء Destructor بشكل صريح إذا كنت تفعل شيئًا غير عادي ، وربما يتضمن استخدام الموضع الجديد.

إذا كنت بحاجة حقًا إلى القيام بشيء من هذا القبيل ، فما عليك سوى إنشاء وظيفة إضافية واتصل بها من الخارج ومن المنشئ نفسه ، ولكن دعنا نرى ما يحدث عندما تحتاج إلى مثل هذه المكالمة:

#include<new>

class A
{
//members
};

int main()
{
//allocate buffer
char* buffer = new char[sizeof(A)];
//construct A on that memory space
A * ptrToA = ::new (buffer) A();
//destroy the object
ptrToA->~A();
//deallocate the buffer
delete[] buffer;
}

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

جرب هذا:

obj.classname :: className () ؛ // يعمل في برنامج التحويل البرمجي VC 6.0

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

المُنشئ هناك "C ()" يستخدم مع جديد ، أي

c objC = new c();

إذا كنت ترغب في الاتصال بمنشئك خارج البناء الفعلي لمثيل الفصل ، فأنت إما لم تفهم الغرض من المُنشئ أو تحاول وضع الوظائف هناك التي لا ينبغي أن تكون هناك.

أنت تسأل عن نمط معين من بناء الجملة أكثر من قيود اللغة. يتيح لك C ++ استدعاء مُنشئ الكائن أو Destructor.

c objC;
objC.~c();  // force objC's destructor to run
new(&objC); // force objC's constructor to run

لماذا لم يستخدم مصممو اللغة هذا بناء الجملة بدلاً من ذلك؟

c objC;
delete(&objC) c; // force objC's destructor to run
new(&objC) c;    // force objC's constructor to run

أو لماذا لا:

c objC
objC.~c(); // force objC's destructor to run
objC.c();  // force objC's constructor to run

رأيي حول سبب اختيارهم لبناء الجملة الذي فعلوه: البديل الأول أكثر مرونة من الثانية. يمكنني استدعاء المُنشئ / المدمر على أي عنوان وليس مجرد مثيل. هذه المرونة مطلوبة للتخصيص ، ولكن ليس للتدمير. كما يبدو أن حذف الخيار الأول أمر خطير للغاية بمجرد دخول المدمرين الظاهريين إلى الفوضى.

يمكنك استدعاء مُنشئ فئة المثيل باستخدام typeof:

class c
{
public:
   void add() ;
   c();
   ~c() ;
};

void main()
{
 c objC  ;
 objC.add() ;
 objC.~c() ; // this line compiles (but is a bad idea)
 typeof objC otherObjC;  // so does this.
}

هذا لا يؤثر على قيمة المثيل objC, ، ولكن يخلق مثيلًا جديدًا otherObjC استخدام objCمُنشئ فئة.

ملاحظة: قد يفعل هذا شيئًا لا تتوقعه إذا كان النوع الثابت عبارة عن نوع من النوع الديناميكي من المثيل الذي لديك.

من يقول أنك لا تستطيع؟ فقط يجب ان تعلم كيف.

void Foo::Bar() {
  *this = Foo(); // Reset *this
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top