c++ - لماذا يجب علي استخدام مؤشر بدلاً من الكائن نفسه؟




pointers c++11 (15)

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

Object *myObject = new Object;

بدلا من:

Object myObject;

أو بدلاً من استخدام دالة ، لنفترض testFunc() ، على testFunc() :

myObject.testFunc();

علينا أن نكتب:

myObject->testFunc();

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


لكن لا أستطيع معرفة لماذا يجب أن نستخدمها هكذا؟

سأقارن كيف يعمل داخل جسم الوظيفة ، إذا كنت تستخدم:

Object myObject;

داخل وظيفة ، سيتم تدمير myObject الخاص بك مرة واحدة بمجرد عودة هذه الوظيفة. لذلك هذا مفيد إذا كنت لا تحتاج إلى كائن خارج وظيفتك. سيتم وضع هذا الكائن على مكدس مؤشر الترابط الحالي.

إذا كتبت داخل جسم الوظيفة:

 Object *myObject = new Object;

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

الآن إذا كنت مبرمج جافا ، فإن المثال الثاني أقرب إلى كيفية عمل تخصيصات الكائنات تحت جافا. هذا الخط: Object *myObject = new Object; يعادل جافا: Object myObject = new Object(); . الفرق هو أنه في ظل java myObject سوف تحصل على جمع القمامة ، بينما تحت C ++ لن يتم تحريرها ، يجب عليك في مكان ما الاتصال بصراحة `حذف myObject ؛ ' وإلا سوف إدخال تسرب الذاكرة.

نظرًا لأن c ++ 11 يمكنك استخدام طرق آمنة للتوزيعات الديناميكية: new Object ، عن طريق تخزين القيم في shared_ptr / unique_ptr.

std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");

// since c++14
std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared"); 

أيضًا ، يتم تخزين الكائنات في حاويات كثيرة جدًا ، مثل map-s أو vector-s ، وستقوم تلقائيًا بإدارة عمر الكائنات الخاصة بك.


مقدمة

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

فصاعدا نذهب

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

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

{
    std::string s;
}
// s is destroyed here

من ناحية أخرى ، إذا كنت تستخدم المؤشر بشكل ديناميكي ، يجب استدعاء destructor يدوياً. delete يدعو هذا destructor بالنسبة لك.

{
    std::string* s = new std::string;
}
delete s; // destructor called

هذا ليس له علاقة ببناء الجملة new السائد في C # و Java. يتم استخدامها لأغراض مختلفة تماما.

فوائد التخصيص الديناميكي

1. ليس عليك معرفة حجم الصفيف مقدمًا

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

char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow

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

int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];

ملاحظة جانبية : خطأ واحد كثير من المبتدئين هو استخدام صفائف طول متغير. هذا هو امتداد GNU وأيضاً في Clang لأنها تعكس العديد من ملحقات دول مجلس التعاون الخليجي. لذلك لا ينبغي الاعتماد على int arr[n] .

نظرًا لأن الكومة أكبر بكثير من الكومة ، يمكن للمرء أن يعزل / يعيد تخصيص قدر الذاكرة التي يحتاجها / ها ، في حين أن الرصة لها قيود.

2. صفائف ليست مؤشرات

كيف هذه فائدة تسأل؟ سوف تصبح الإجابة واضحة بمجرد فهمك للارتباك / الأسطورة وراء المصفوفات والمؤشرات. من المفترض عادة أن تكون هي نفسها ، لكنها ليست كذلك. تأتي هذه الأسطورة من حقيقة أن المؤشرات يمكن أن تكون مشفرة مثل المصفوفات ولأن المصفوفات تتحلل إلى مؤشرات على المستوى الأعلى في إعلان الدالة. ومع ذلك ، عندما يتحلل صفيف إلى مؤشر ، يفقد المؤشر معلومات حجمه. لذا سيعطيك sizeof(pointer) حجم المؤشر بالبايت ، وهو عادة 8 بايت على نظام 64 بت.

لا يمكنك التعيين إلى المصفوفات ، فقم بتهيئتها فقط. فمثلا:

int arr[5] = {1, 2, 3, 4, 5}; // initialization 
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
                             // be given by the amount of members in the initializer  
arr = { 1, 2, 3, 4, 5 }; // ERROR

من ناحية أخرى ، يمكنك القيام بكل ما تريد من خلال المؤشرات. لسوء الحظ ، لأن التمييز بين المؤشرات والصفائف محفور باليد في Java و C # ، لا يفهم المبتدئين الفرق.

3. تعدد الأشكال

تحتوي Java و C # على تسهيلات تتيح لك التعامل مع الكائنات كأشياء أخرى ، على سبيل المثال باستخدام الكلمة الأساسية. لذلك إذا أراد شخص ما التعامل مع كائن Entity ككائن Player ، فيمكن أن يفعل Player player = Entity as Player; هذا مفيد للغاية إذا كنت تنوي استدعاء وظائف على حاوية متجانسة يجب أن تنطبق فقط على نوع معين. يمكن تحقيق الوظيفة بطريقة مشابهة أدناه:

std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
     auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
     if (!test) // not a triangle
        e.GenericFunction();
     else
        e.TriangleOnlyMagic();
}

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

فوائد المتغيرات التلقائية

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

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

  • ليست ضرورية. بخلاف Java و C # حيث يكون اصطلاحيًا استخدام الكلمة الرئيسية new كل مكان ، في C ++ ، يجب عليك استخدامها فقط إذا كنت بحاجة إلى ذلك. العبارة الشائعة تقول ، كل شيء يبدو وكأنه مسمار إذا كان لديك مطرقة. في حين أن المبتدئين الذين يبدأون بـ C ++ يخافون من المؤشرات ويتعلمون استخدام متغيرات المكدس عن طريق العادة ، يبدأ مبرمجو Java و C # باستخدام المؤشرات دون فهمها! هذا هو حرفيا تنطلق على قدم خاطئة. يجب عليك التخلي عن كل ما تعرفه لأن بناء الجملة شيء واحد ، تعلم اللغة هو شيء آخر.

1. (N) RVO - Aka ، (المسماة) تحسين قيمة الإرجاع

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

إذا كنت تستخدم المؤشرات ، لا يحدث (N) RVO. ومن أكثر فائدة وأقل عرضة للخطأ للاستفادة من (N) RVO بدلا من العودة أو تمرير مؤشرات إذا كنت قلقا بشأن التحسين. يمكن أن يحدث تسرب الأخطاء إذا كان المتصل من الدالة مسؤولاً عن delete كائن مضمن بشكل ديناميكي. قد يكون من الصعب تعقب ملكية كائن إذا تم تمرير المؤشرات مثل البطاطا الساخنة. استخدم فقط متغيرات المكدس لأنها أبسط وأفضل.


في C ++ ، الكائنات الموجودة على المكدس (باستخدام Object object; عبارة ضمن كتلة) ستعيش فقط ضمن النطاق الذي يتم التصريح به. عند انتهاء كتلة التعليمات البرمجية ، يتم إتلاف الكائن المُعلن. بينما إذا قمت بتخصيص ذاكرة على كومة الذاكرة المؤقتة ، باستخدام Object* obj = new Object() ، فإنها تستمر في العيش في كومة الذاكرة المؤقتة حتى تقوم باستدعاء delete obj .

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


من المؤسف أنك ترى التوزيع الديناميكي في كثير من الأحيان. هذا يظهر فقط عدد المبرمجين السيئين ++ C هناك.

بمعنى من المعاني ، لديك سؤالان مجمعين في واحد. الأول هو متى يجب أن نستخدم التخصيص الديناميكي (باستخدام new )؟ والثاني هو متى يجب أن نستخدم المؤشرات؟

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

التخصيص الديناميكي

في سؤالك ، لقد أظهرت طريقتين لإنشاء كائن. والفرق الرئيسي هو مدة تخزين الكائن. عندما تفعل Object myObject; داخل كتلة ، يتم إنشاء الكائن مع مدة التخزين التلقائي ، مما يعني أنه سيتم تدميره تلقائيًا عندما يخرج عن نطاقه. عندما تقوم بعمل new Object() ، فإن الكائن به مدة تخزين ديناميكية ، مما يعني أنه يبقى على قيد الحياة حتى تقوم delete بشكل صريح. يجب عليك فقط استخدام مدة التخزين الديناميكية عندما تحتاج إليها. أي ، يجب دائمًا تفضيل إنشاء كائنات ذات مدة تخزين تلقائية عندما تستطيع ذلك .

الموقفان الرئيسيان اللذان قد تحتاج فيهما إلى تخصيص ديناميكي:

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

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

مؤشرات

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

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

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

  3. تريد أن تمثل أن الكائن اختياري عن طريق السماح nullptr عند حذف الكائن. إذا كانت عبارة عن وسيطة ، فيجب أن تفضل استخدام الوسيطات الافتراضية أو الحمل الزائد للدالة. خلاف ذلك ، يجب أن تفضل استخدام نوع بتغليف هذا السلوك ، مثل std::optional (تم تقديمه في C ++ 17 - مع معايير C ++ السابقة ، استخدم boost::optional ).

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

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


هناك العديد من الإجابات الممتازة على هذا السؤال ، بما في ذلك حالات الاستخدام الهامة للإعلانات المتقدمة ، تعدد الأشكال إلخ. لكني أشعر أن جزءًا من "روح" سؤالك لا يتم الإجابة عليه - أي ما تعنيه الجمل المختلفة عبر جافا و C ++.

دعونا نفحص الوضع الذي يقارن بين اللغتين:

جافا:

Object object1 = new Object(); //A new object is allocated by Java
Object object2 = new Object(); //Another new object is allocated by Java

object1 = object2; 
//object1 now points to the object originally allocated for object2
//The object originally allocated for object1 is now "dead" - nothing points to it, so it
//will be reclaimed by the Garbage Collector.
//If either object1 or object2 is changed, the change will be reflected to the other

أقرب ما يعادل هذا ، هو:

C ++:

Object * object1 = new Object(); //A new object is allocated on the heap
Object * object2 = new Object(); //Another new object is allocated on the heap
delete object1;
//Since C++ does not have a garbage collector, if we don't do that, the next line would 
//cause a "memory leak", i.e. a piece of claimed memory that the app cannot use 
//and that we have no way to reclaim...

object1 = object2; //Same as Java, object1 points to object2.

لنرى طريقة C ++ البديلة:

Object object1; //A new object is allocated on the STACK
Object object2; //Another new object is allocated on the STACK
object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1,
//using the "copy assignment operator", the definition of operator =.
//But, the two objects are still different. Change one, the other remains unchanged.
//Also, the objects get automatically destroyed once the function returns...

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

جافا:

int object1; //An integer is allocated on the stack.
int object2; //Another integer is allocated on the stack.
object1 = object2; //The value of object2 is copied to object1.

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

خذ نقطة المنزل - Object * object = new Object() بناء هو في الواقع ما هو أقرب إلى semantics Java (أو C # لهذا الأمر) النموذجي.


هناك العديد من حالات الاستخدام للمؤشرات.

سلوك متعدد الأشكال . بالنسبة للأنواع متعددة الأشكال ، يتم استخدام المؤشرات (أو المراجع) لتجنب التقسيم:

class Base { ... };
class Derived : public Base { ... };

void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }

Derived d;
fun(d);    // oops, all Derived parts silently "sliced" off
gun(&d);   // OK, a Derived object IS-A Base object
hun(d);    // also OK, reference also doesn't slice

دلالات مرجعية وتجنب النسخ . بالنسبة للأنواع غير متعددة الأشكال ، سيتجنب المؤشر (أو المرجع) نسخ كائن قد يكون باهظ الثمن

Base b;
fun(b);  // copies b, potentially expensive 
gun(&b); // takes a pointer to b, no copying
hun(b);  // regular syntax, behaves as a pointer

لاحظ أن C ++ 11 يحتوي على دلالات نقل يمكن أن تتجنب العديد من نسخ الكائنات الغالية إلى وسيطة دالة وكقيم إرجاع. ولكن استخدام مؤشر سيؤدي بالتأكيد إلى تجنب تلك وسيسمح بعدة مؤشرات على نفس الكائن (في حين أن الكائن يمكن نقله من مرة واحدة فقط).

اكتساب الموارد . إنشاء مؤشر إلى مورد باستخدام المشغل new هو نمط مكافحة في C ++ الحديثة. استخدام فئة مورد خاص (إحدى حاويات قياسي) أو مؤشر ذكي ( std::unique_ptr<> أو std::shared_ptr<> ). يعتبر:

{
    auto b = new Base;
    ...       // oops, if an exception is thrown, destructor not called!
    delete b;
}

ضد.

{
    auto b = std::make_unique<Base>();
    ...       // OK, now exception safe
}

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

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


"Necessity is the mother of invention." The most of important difference that I would like to point out is the outcome of my own experience of coding. Sometimes you need to pass objects to functions . In that case if your object is of a very big class then passing it as an object will copy its state (which you might not want ..AND CAN BE BIG OVERHEAD) thus resulting in overhead of copying object .while pointer is fixed 4 byte size (assuming 32 bit).Other reasons are already mentioned above...


With pointers ,

  • can directly talk to the memory.

  • can prevent lot of memory leaks of a program by manipulating pointers.


I will include one important use case of pointer. When you are storing some object in the base class, but it could be polymorphic.

Class Base1 {
};

Class Derived1 : public Base1 {
};


Class Base2 {
  Base *bObj;
  virtual void createMemerObects() = 0;
};

Class Derived2 {
  virtual void createMemerObects() {
    bObj = new Derived1();
  }
};

So in this case you can't declare bObj as an direct object, you have to have pointer.


In areas where memory utilization is at its premium , pointers comes handy. For example consider a minimax algorithm, where thousands of nodes will be generated using recursive routine, and later use them to evaluate the next best move in game, ability to deallocate or reset (as in smart pointers) significantly reduces memory consumption. Whereas the non-pointer variable continues to occupy space till it's recursive call returns a value.


One reason for using pointers is to interface with C functions. Another reason is to save memory; for example: instead of passing an object which contains a lot of data and has a processor-intensive copy-constructor to a function, just pass a pointer to the object, saving memory and speed especially if you're in a loop, however a reference would be better in that case, unless you're using an C-style array.


There are many benefits of using pointers to object -

  1. Efficiency (as you already pointed out). Passing objects to functions mean creating new copies of object.
  2. Working with objects from third party libraries. If your object belongs to a third party code and the authors intend the usage of their objects through pointers only (no copy constructors etc) the only way you can pass around this object is using pointers. Passing by value may cause issues. (Deep copy / shallow copy issues).
  3. if the object owns a resource and you want that the ownership should not be sahred with other objects.

This is has been discussed at length, but in Java everything is a pointer. It makes no distinction between stack and heap allocations (all objects are allocated on the heap), so you don't realize you're using pointers. In C++, you can mix the two, depending on your memory requirements. Performance and memory usage is more deterministic in C++ (duh).


Well the main question is Why should I use a pointer rather than the object itself? And my answer, you should (almost) never use pointer instead of object, because C++ has references , it is safer then pointers and guarantees the same performance as pointers.

Another thing you mentioned in your question:

Object *myObject = new Object;

كيف يعمل؟ It creates pointer of Object type, allocates memory to fit one object and calls default constructor, sounds good, right? But actually it isn't so good, if you dynamically allocated memory (used keyword new ), you also have to free memory manually, that means in code you should have:

delete myObject;

This calls destructor and frees memory, looks easy, however in big projects may be difficult to detect if one thread freed memory or not, but for that purpose you can try shared pointers , these slightly decreases performance, but it is much easier to work with them.

And now some introduction is over and go back to question.

You can use pointers instead of objects to get better performance while transferring data between function.

Take a look, you have std::string (it is also object) and it contains really much data, for example big XML, now you need to parse it, but for that you have function void foo(...) which can be declarated in different ways:

  1. void foo(std::string xml); In this case you will copy all data from your variable to function stack, it takes some time, so your performance will be low.
  2. void foo(std::string* xml); In this case you will pass pointer to object, same speed as passing size_t variable, however this declaration has error prone, because you can pass NULL pointer or invalid pointer. Pointers usually used in C because it doesn't have references.
  3. void foo(std::string& xml); Here you pass reference, basically it is the same as passing pointer, but compiler does some stuff and you cannot pass invalid reference (actually it is possible to create situation with invalid reference, but it is tricking compiler).
  4. void foo(const std::string* xml); Here is the same as second, just pointer value cannot be changed.
  5. void foo(const std::string& xml); Here is the same as third, but object value cannot be changed.

What more I want to mention, you can use these 5 ways to pass data no matter which allocation way you have chosen (with new or regular ).

Another thing to mention, when you create object in regular way, you allocate memory in stack, but while you create it with new you allocate heap. It is much faster to allocate stack, but it is kind a small for really big arrays of data, so if you need big object you should use heap, because you may get , but usually this issue is solved using STL containers and remember std::string is also container, some guys forgot it :)


Object *myObject = new Object;

Doing this will create a reference to an Object (on the heap) which has to be deleted explicitly to avoid memory leak .

Object myObject;

Doing this will create an object(myObject) of the automatic type (on the stack) that will be automatically deleted when the object(myObject) goes out of scope.





c++11