c++ - شرح - ما هي القواعد والتعابير الأساسية للحمولة الزائدة على المشغل؟




postfix overloading operator c++ (5)

ملاحظة: تم تقديم الإجابات بترتيب معين ، ولكن نظرًا لأن العديد من المستخدمين يقومون بفرز الإجابات وفقًا للأصوات ، بدلاً من الوقت الذي تم منحهم فيه ، في ما يلي فهرس للإجابات بالترتيب الذي يكونون فيه أكثر منطقية:

(ملاحظة: من المفترض أن يكون هذا الدخول إلى الأسئلة المتداولة في C ++ الخاص بـ Stack Overflow . إذا كنت ترغب في انتقاد فكرة تقديم الأسئلة الشائعة في هذا النموذج ، فإن النشر على meta الذي بدأ كل هذا سيكون المكان المناسب للقيام بذلك. يتم رصد هذا السؤال في غرفة الدردشة C ++ ، حيث بدأت فكرة الأسئلة الشائعة في المقام الأول ، لذلك فمن المحتمل جدًا أن تتم قراءة إجابتك من قبل أولئك الذين توصلوا إلى الفكرة.)


الحمولة الزائدة new delete

ملاحظة: هذا لا يتعامل إلا مع صيغة زيادة الحمولة new delete ، وليس مع تنفيذ مثل هذه المشغلات ذات التحميل الزائد. أعتقد أن دلالات التحميل الزائد new delete تستحق الأسئلة الشائعة الخاصة بها ، داخل موضوع الحمولة الزائدة من المشغل ، لا يمكنني أبداً أن أفعلها.

مبادئ

في C ++ ، عندما تكتب تعبيرًا جديدًا مثل new T(arg) يحدث أمران عند تقييم هذا التعبير: يتم استدعاء operator new الأول operator new للحصول على ذاكرة خام ، ثم يتم استدعاء المُنشئ المناسب لـ T لتحويل هذه الذاكرة الخام إلى كائن صالح. وبالمثل ، عندما تقوم بحذف كائن ، يتم استدعاء destructor أولاً ، ثم يتم إرجاع الذاكرة إلى operator delete .
يسمح لك C ++ بتوليف كل من هذه العمليات: إدارة الذاكرة وإنشاء / إتلاف الكائن في الذاكرة المخصصة. ويتم هذا الأخير من خلال كتابة صناع ومدمرين لفئة. تتم إدارة الذاكرة بدقة عن طريق كتابة operator new operator delete الخاص بك.

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

تأتي المكتبة القياسية C ++ مع مجموعة من عوامل التشغيل new delete المعرفة مسبقاً. أهم هذه هي:

void* operator new(std::size_t) throw(std::bad_alloc); 
void  operator delete(void*) throw(); 
void* operator new[](std::size_t) throw(std::bad_alloc); 
void  operator delete[](void*) throw(); 

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

وضع new

يسمح C ++ لمشغلين جدد وحذف بأخذ وسائط إضافية.
يسمح لك وضع ما يسمى بالموضع الجديد بإنشاء كائن في عنوان معين يتم تمريره إلى:

class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{ 
  X* p = new(buffer) X(/*...*/);
  // ... 
  p->~X(); // call destructor 
} 

تأتي المكتبة القياسية مع الحمولات الزائدة المناسبة للمشغلين الجدد والمحذوفين لهذا:

void* operator new(std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete(void* p,void*) throw(); 
void* operator new[](std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete[](void* p,void*) throw(); 

ﻻﺣ Note أﻧﻪ ﻓﻲ ﻣﺜﺎل رﻣﺰ اﻟﻤﻠﻒ اﻟﺨﺎص ﺑﺎﻟﻤﻮﺿﻊ اﻟﺠﺪﻳﺪ اﻟﻤﻌﻄﻰ أﻋﻼﻩ ، ﻻ ﻳﺘﻢ اﺳﺘﺪﻋﺎء operator delete ، ﻣﺎ ﻟﻢ ﻳﻠﻘﻲ ﻣﻨﺸﻮر X اﺳﺘﺜﻨﺎءً.

يمكنك أيضًا التحميل الزائد delete باستخدام الوسيطات الأخرى. كما هو الحال مع الوسيطة الإضافية للموضع الجديد ، يتم أيضًا سرد هذه الوسيطات بين قوسين بعد الكلمة الأساسية new . فقط لأسباب تاريخية ، غالبًا ما تسمى هذه المتغيرات أيضًا بالتعيين الجديد ، حتى إذا لم تكن حججها لوضع كائن في عنوان محدد.

فئة محددة جديدة وحذفها

Most commonly you will want to fine-tune memory management because measurement has shown that instances of a specific class, or of a group of related classes, are created and destroyed often and that the default memory management of the run-time system, tuned for general performance, deals inefficiently in this specific case. To improve this, you can overload new and delete for a specific class:

class my_class { 
  public: 
    // ... 
    void* operator new();
    void  operator delete(void*,std::size_t);
    void* operator new[](size_t);
    void  operator delete[](void*,std::size_t);
    // ... 
}; 

Overloaded thus, new and delete behave like static member functions. For objects of my_class , the std::size_t argument will always be sizeof(my_class) . However, these operators are also called for dynamically allocated objects of derived classes , in which case it might be greater than that.

Global new and delete

To overload the global new and delete, simply replace the pre-defined operators of the standard library with our own. However, this rarely ever needs to be done.


القواعد الأساسية الثلاثة للحمولة الزائدة في المشغل في C ++

عندما يتعلق الأمر بالحمل الزائد في C ++ ، هناك ثلاثة قواعد أساسية يجب عليك اتباعها . كما هو الحال مع جميع هذه القواعد ، هناك استثناءات في الواقع. في بعض الأحيان يكون الناس قد انحرفوا عنهم وكانت النتيجة ليست مدونة سيئة ، ولكن مثل هذه الانحرافات الإيجابية قليلة ومتباعدة. على أقل تقدير ، كان 99 من أصل 100 من هذه الانحرافات التي رأيتها غير مبررة. ومع ذلك ، قد يكون 999 من أصل 1000. لذا من الأفضل الالتزام بالقواعد التالية.

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

  2. دائما التزم بدلالات المشغل المعروفة.
    لا تفرض C ++ أي قيود على دلالات مشغلي overloaded. سيقبل المحول البرمجي الخاص بك بسرور التعليمات البرمجية التي تقوم بتنفيذ مشغل ثنائي + لطرح من المعامل الأيمن الخاص به. ومع ذلك ، فإن مستخدمي مثل هذا المشغل لا يشك أبدًا في تعبير a + b لطرح a من b . وبطبيعة الحال ، يفترض هذا أن دلالات المشغل في مجال التطبيق غير متنازع عليها.

  3. دائما تقديم كل من مجموعة من العمليات ذات الصلة.
    يرتبط المشغلون ببعضهم البعض وبالعمليات الأخرى. إذا كان نوعك يدعم a + b ، فسيتوقع المستخدمون أن يكون بإمكانهم الاتصال a += b أيضًا. إذا كان يدعم بادئة الزيادة ++a ، فإنهم يتوقعون أن يعمل a++ أيضًا. إذا تمكنوا من التحقق مما إذا كان a < b ، فمن المؤكد أنهم يتوقعون أيضًا أن يكونوا قادرين على التحقق مما إذا كانت a > b . إذا تمكنوا من نسخ بناء نوعك ، فإنهم يتوقعون أن يعمل الواجب.

الاستمرار في القرار بين العضو وغير الأعضاء .


بناء الجملة العام للحمولة الزائدة في المشغل في C ++

لا يمكنك تغيير معنى عوامل التشغيل لأنواع المضمنة في C ++ ، يمكن فقط تحميل مشغلي البيانات لأنواع المعرفة من قبل المستخدم 1 . بمعنى ، يجب أن يكون واحد على الأقل من المعاملات من نوع المعرفة من قبل المستخدم. وكما هو الحال مع الوظائف الأخرى ذات التحميل الزائد ، يمكن تحميل المشغلين على مجموعة معينة من المعلمات مرة واحدة فقط.

لا يمكن تحميل جميع المشغلين في C ++. من بين المشغلين الذين لا يمكن تحميلهم بشكل زائد:. :: sizeof typeid .* والمشغل الثلاثي الوحيد في لغة C ++ ، ?:

بين المشغلين التي يمكن أن تكون محملة بشكل زائد في C ++ هي:

  • العوامل الحسابية: + - * / % و += -= *= /= %= (جميع اللاحقة الثنائية)؛ + - (البادئة الأحادية) ؛ ++ -- (البادئة الأحادية والبادئة)
  • التلاعب قليلا: & | ^ << >> and &= |= ^= <<= >>= (جميع اللاحقة الثنائية)؛ ~ (البادئة الأحادية)
  • الجبر البولي: == != < > <= >= || && (جميع النسخ الثنائية) ؛ ! (البادئة الأحادية)
  • إدارة الذاكرة: new new[] delete delete[]
  • مشغلي التحويل الضمني
  • miscellany: = [] -> ->* , (جميع binary infix)؛ * & (جميع البادئة الأحادية) () (استدعاء دالة ، n-ary infix)

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

في C ++ ، يتم تحميل المشغلين في شكل وظائف ذات أسماء خاصة . كما هو الحال مع الوظائف الأخرى ، يمكن تنفيذ مشغلي التحميل الزائد بشكل عام إما كدالة عضو لنوع المعامل الأيسر أو كدالات غير عضو . سواء كنت حرًا في الاختيار أو الإلزام باستخدام أحدهما يعتمد على عدة معايير. 2 يتم استخدام عامل التشغيل أحادي @ 3 ، المطبق على كائن x ، إما [email protected]() [email protected](x) أو كـ [email protected]() . يسمى [email protected](x,y) [email protected](y) ثنائي ، المطبق على الكائنات x و y ، إما [email protected](y) [email protected](x,y) أو كـ [email protected](y) . 4

المشغلين التي يتم تنفيذها على أنها وظائف غير الأعضاء تكون أحيانًا صديقة لنوع المعامل الخاص بها.

1 قد يكون مصطلح "معرفة من قبل المستخدم" مضللًا بعض الشيء. يجعل C ++ التمييز بين الأنواع المضمنة والأنواع المعرفة من قبل المستخدم. إلى تنتمي السابق على سبيل المثال ، int ، شار ، ومضاعفة ؛ وينتمي هذا الأخير إلى كل أنواع البنية ، والفئة ، والاتحاد ، وأنواع التعداد ، بما في ذلك تلك الموجودة في المكتبة القياسية ، على الرغم من أنها غير معرّفة من قبل المستخدمين.

2 يتم تناول هذا في جزء لاحق من هذه الأسئلة الشائعة.

3 إن @ ليس مشغلًا صالحًا في C ++ ، ولهذا السبب استخدمه كعنصر نائب.

4 لا يمكن تحميل المشغل الثلاثي الوحيد في لغة C ++ عبءًا زائدًا ، ويجب دائمًا تنفيذ مشغل n-ary الوحيد كدولة عضو.

الاستمرار في القواعد الثلاثة الأساسية للحمولة الزائدة في المشغل في C ++ .


مشغِّل التحويل (يُعرف أيضًا باسم التحويلات المحددة من قِبل المستخدم)

في C ++ يمكنك إنشاء عوامل تشغيل تحويل ، عوامل تشغيل تسمح لمحول التحويل بالتحويل بين أنواعك وأنواع محددة أخرى. هناك نوعان من عوامل التحويل ، هما عوامل ضمنية وصريحة.

مشغّلو التحويل الضمني (C ++ 98 / C ++ 03 و C ++ 11)

يسمح مشغل التحويل الضمني للمحول بتحويل (مثل التحويل بين int و long ) قيمة نوع المعرفة من قبل المستخدم إلى نوع آخر.

ما يلي هو فئة بسيطة مع مشغل تحويل ضمني:

class my_string {
public:
  operator const char*() const {return data_;} // This is the conversion operator
private:
  const char* data_;
};

عوامل التحويل الضمنية ، مثل مبادلات الوسيطة الواحدة ، هي تحويلات المعرفة من قبل المستخدم. سوف يمنح المجمعين تحويل واحد معرف من قبل المستخدم عند محاولة مطابقة استدعاء دالة overloaded.

void f(const char*);

my_string str;
f(str); // same as f( str.operator const char*() )

في البداية يبدو هذا مفيدًا جدًا ، ولكن المشكلة في هذا أن التحويل الضمني ينطلق حتى عندما لا يكون متوقعًا. في التعليمة البرمجية التالية ، سيتم استدعاء my_string() void f(const char*) لأن my_string() ليس عبارة عن lvalue ، لذلك لا يتطابق الأول:

void f(my_string&);
void f(const char*);

f(my_string());

المبتدئين بسهولة الحصول على هذا الخطأ ، وحتى المفاجأة حتى مبرمجين C ++ من ذوي الخبرة في بعض الأحيان لأن المجمع يختار الزائد لم يشكوا. يمكن التخفيف من هذه المشاكل بواسطة مشغلي التحويل الواضح.

مشغّل التحويل الصريح (C ++ 11)

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

class my_string {
public:
  explicit operator const char*() const {return data_;}
private:
  const char* data_;
};

لاحظ explicit . الآن عند محاولة تنفيذ التعليمات البرمجية غير المتوقعة من عوامل التحويل الضمنية ، تحصل على خطأ في برنامج التحويل البرمجي:

prog.cpp: In function ‘int main()’:
prog.cpp:15:18: error: no matching function for call to ‘f(my_string)’
prog.cpp:15:18: note: candidates are:
prog.cpp:11:10: note: void f(my_string&)
prog.cpp:11:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘my_string&’
prog.cpp:12:10: note: void f(const char*)
prog.cpp:12:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘const char*’

لاستدعاء مشغل static_cast ، يجب عليك استخدام static_cast ، أو نمط C-cast ، أو منحنى نمط منشئ (أي T(value) ).

ومع ذلك ، هناك استثناء واحد لهذا: يسمح المترجم لتحويل ضمنا إلى bool . بالإضافة إلى ذلك ، لا يُسمح للمُجمّع بإجراء تحويل ضمني آخر بعد تحويله إلى bool (يُسمح لأحد المترجمين بإجراء عمليتي تحويل ضمنيتين في المرة الواحدة ، مع تحويل واحد فقط من قبل المستخدم بحد أقصى).

نظرًا لأن المحول البرمجي لن يلقي bool "السابق" ، فإن مشغلي التحويل الصريح يزيلون الآن الحاجة إلى عبارات Safe Bool . على سبيل المثال ، استخدمت المؤشرات الذكية قبل C ++ 11 عبارة Bool Safe لمنع التحويلات إلى الأنواع المتكاملة. في C ++ 11 ، تستخدم المؤشرات الذكية مشغلًا صريحًا بدلاً من ذلك لأن المحول البرمجي غير مسموح له بالتحويل ضمنيًا إلى نوع متكامل بعد أن يقوم بتحويل نوع إلى bool بشكل صريح.

استمر في التحميل new delete .


Why can't operator<< function for streaming objects to std::cout or to a file be a member function?

Let's say you have:

struct Foo
{
   int a;
   double b;

   std::ostream& operator<<(std::ostream& out) const
   {
      return out << a << " " << b;
   }
};

Given that, you cannot use:

Foo f = {10, 20.0};
std::cout << f;

Since operator<< is overloaded as a member function of Foo , the LHS of the operator must be a Foo object. Which means, you will be required to use:

Foo f = {10, 20.0};
f << std::cout

which is very non-intuitive.

If you define it as a non-member function,

struct Foo
{
   int a;
   double b;
};

std::ostream& operator<<(std::ostream& out, Foo const& f)
{
   return out << f.a << " " << f.b;
}

You will be able to use:

Foo f = {10, 20.0};
std::cout << f;

وهو بديهي جدا.





c++-faq