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




operator overloading (7)

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

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


Answers

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;

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


المشغلين المشتركة لزيادة الحمل

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

مهمة تشغيل

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

X& X::operator=(X rhs)
{
  swap(rhs);
  return *this;
}

مشغلات Bitshift (تُستخدم لـ Stream I / O)

إن مشغلي وحدات bitshift << و >> ، رغم أنهم ما زالوا يستخدمون في التوصيل البيني للأجهزة لوظائف التلاعب في البتات التي يرثونها من C ، فقد أصبحوا أكثر انتشاراً مع مشغلي تدفق المدخلات والمخرجات في معظم التطبيقات. للحصول على الحمولة الزائدة للتوجيه كمشغلين لمعالجة البتات ، راجع القسم أدناه الخاص بمشغلي الحساب الثنائي. لتنفيذ التنسيق المخصص الخاص بك وتحليل منطق عندما يتم استخدام الكائن الخاص بك مع iostreams ، تابع.

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

std::ostream& operator<<(std::ostream& os, const T& obj)
{
  // write obj to stream

  return os;
}

std::istream& operator>>(std::istream& is, T& obj)
{
  // read obj from stream

  if( /* no valid object of T found in stream */ )
    is.setstate(std::ios::failbit);

  return is;
}

عند تنفيذ operator>> ، فإن ضبط حالة الدفق يدويًا ضروري فقط عندما تنجح القراءة نفسها ، ولكن النتيجة ليست ما هو متوقع.

عامل الاتصال الوظيفي

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

في ما يلي مثال على البنية:

class foo {
public:
    // Overloaded call operator
    int operator()(const std::string& y) {
        // ...
    }
};

الاستعمال:

foo f;
int a = f("hello");

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

عوامل المقارنة

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

إن خوارزميات المكتبة القياسية (مثل std::sort() وأنواع (على سبيل المثال std::map ) ستتوقع دائمًا فقط أن يكون operator< . ومع ذلك ، فإن مستخدمي نوعك يتوقعون وجود جميع المشغلين الآخرين أيضًا ، لذلك إذا قمت بتعريف operator< ، تأكد من اتباع القاعدة الأساسية الثالثة للحمولة الزائدة على المشغل وكذلك تحديد جميع عوامل المقارنة المقارنة الأخرى. الطريقة الكنسيّة لتطبيقها هي:

inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return  operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}

من المهم أن نلاحظ هنا أن اثنين فقط من هؤلاء المشغلين يقومون بأي شيء بالفعل ، بينما يقوم الآخرون فقط بإرسال حججهم إلى أي من هذين العاملين للقيام بالعمل الفعلي.

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

1 كما هو الحال مع جميع القواعد الأساسية ، قد تكون هناك أحيانًا أسباب لكسر هذه القاعدة أيضًا. إذا كان الأمر كذلك ، فلا تنسَ أن المعامل الأيسر لمشغلي المقارنة الثنائية ، والذي سيكون بالنسبة لوظائف الأعضاء *this ، يجب أن يكون const أيضًا. لذا فإن مشغل المقارنة الذي تم تنفيذه كوظيفة عضو يجب أن يكون له هذا التوقيع:

bool operator<(const X& rhs) const { /* do actual comparison with *this */ }

(لاحظ const في النهاية.)

2 وتجدر الإشارة إلى أن النسخة المضمنة من || و && استخدم دلالات الاختصار. في حين أن المستخدم المعرفة (لأنها سكر نحوي لمكالمات الأسلوب) لا تستخدم دلالات الاختصار. يتوقع المستخدم أن يكون لدى هذه المشغلات دلالات اختصارات ، وقد يعتمد عليها الكود ، لذلك ينصح بشدة بعدم تعريفها.

العوامل الحسابية

مشغلي الحساب الأحادي

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

class X {
  X& operator++()
  {
    // do actual increment
    return *this;
  }
  X operator++(int)
  {
    X tmp(*this);
    operator++();
    return tmp;
  }
};

لاحظ أن البديل postfix يتم تنفيذه من حيث البادئة. لاحظ أيضًا أن postfix يقوم بنسخة إضافية. 2

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

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

مشغلي الحساب الثنائي

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

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

class X {
  X& operator+=(const X& rhs)
  {
    // actual addition of rhs to *this
    return *this;
  }
};
inline X operator+(X lhs, const X& rhs)
{
  lhs += rhs;
  return lhs;
}

operator+= يعرض ناتجها لكل مرجع ، بينما يقوم operator+ بإرجاع نسخة من ناتجها. بالطبع ، عادة ما يكون إرجاع المرجع أكثر كفاءة من إرجاع نسخة ، ولكن في حالة operator+ ، لا توجد طريقة حول النسخ. عند كتابة a + b ، تتوقع أن تكون النتيجة قيمة جديدة ، وهذا هو السبب في أن operator+ يجب أن يعرض قيمة جديدة. 3 لاحظ أيضًا أن operator+ يأخذ المعامل الأيسر الخاص به بنسخة بدلاً من مرجع const. السبب في ذلك هو نفس سبب إعطاء operator= أخذ حجه لكل نسخة.

مشغلات التلاعب قليلا ~ & | يجب أن يتم تنفيذ ^ << >> بنفس طريقة المشغّلات الحسابية. ومع ذلك ، (باستثناء الحمولة الزائدة << و >> للإخراج والمدخلات) ، هناك عدد قليل جدًا من حالات الاستخدام المعقولة للتحميل الزائد.

3 مرة أخرى ، فإن الدرس المستفاد من ذلك هو أن a += b بشكل عام أكثر كفاءة من a + b ويجب أن يكون مفضلاً إذا أمكن.

صفيف الاكتتاب

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

class X {
        value_type& operator[](index_type idx);
  const value_type& operator[](index_type idx) const;
  // ...
};

إذا لم تكن ترغب في أن يتمكن مستخدمو صفك من تغيير عناصر البيانات التي يتم إرجاعها من قِبل operator[] (في هذه الحالة يمكنك تجاهل الشكل غير الثابت) ، فيجب عليك دائمًا توفير كلا النوعين من المشغل.

إذا كان value_type معروفًا بالإشارة إلى نوع مدمج ، فيجب أن يقوم متغير const الخاص بالمشغل بإرجاع نسخة بدلاً من مرجع Const.

المشغلين لأنواع تشبه المؤشر

لتعريف المتكررات الخاصة بك أو المؤشرات الذكية ، يجب عليك تحميل عامل dereference البادئة * والفريد من نوعه وعامل وصول عضو مؤشر الوميض الثنائي -> :

class my_ptr {
        value_type& operator*();
  const value_type& operator*() const;
        value_type* operator->();
  const value_type* operator->() const;
};

لاحظ أن هذه ، أيضًا ، ستحتاج دائمًا إلى إصدار ثابت وغير ثابت. بالنسبة الى -> اذا كان نوع value_type (أو value_type أو union ) ، فإن operator->() آخر operator->() يسمى بشكل متكرر ، حتى يقوم operator->() بإرجاع قيمة نوع غير الفئة.

لا ينبغي أبداً تحميل الحمولة الزائدة لعنوان المشغل.

operator->*() راجع هذا السؤال . ونادرا ما يستخدم ، وبالتالي نادرا ما مثقلة. في الحقيقة ، حتى المتكررات لا تفرط في تحميلها.

الاستمرار في مشغلي التحويل


القواعد الأساسية الثلاثة للحمولة الزائدة في المشغل في 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 . إذا تمكنوا من نسخ بناء نوعك ، فإنهم يتوقعون أن يعمل الواجب.

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


الحمولة الزائدة 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 ++ 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 .


بناء الجملة العام للحمولة الزائدة في المشغل في 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 ++ .


Syntax    Name             Description

x == y    Equality         True if x and y have the same key/value pairs
x != y    Inequality       True if x is not equal to y
x === y   Identity         True if x and y have the same key/value pairs
                            in the same order and of the same types
x !== y   Non-identity     True if x is not identical to y
++ x      Pre-increment    Increments x by one, then returns x
x ++      Post-increment   Returns x, then increments x by one
-- x      Pre-decrement    Decrements x by one, then returns x
x --      Post-decrement   Returns x, then decrements x by one
x and y   And              True if both x and y are true x=6 y=3
                           (x < 10 and y > 1) returns true 
x && y    And              True if both x and y are true x=6 y=3
                           (x < 10 && y > 1) returns true
a . b     Concatenation    Concatenate two strings: "Hi" . "Ha"




c++ operators operator-overloading c++-faq