سؤال

class mystring {
 friend ostream& operator<<(ostream &out, const mystring ss) {
        out << ss.s;
        return out;
    }
private:
    string s;
public:
    mystring(const char ss[]) {
        cout << "constructing mystring : " << ss << endl;
        s = ss;
    }
};

void outputStringByRef(const mystring &ss) {
 cout << "outputString(const string& ) " << ss << endl;
}

void outputStringByVal(const mystring ss) {
 cout << "outputString(const string ) " << ss << endl;
}

int main(void) {
    outputStringByRef("string by reference");
    outputStringByVal("string by value");
    outputStringByRef(mystring("string by reference explict call mystring consructor"));
    outputStringByVal(mystring("string by value explict call mystring constructor"));
} ///:~

بالنظر إلى المثال أعلاه ، لم نتمكن من تعديل متغير مرجع مرجع ، لا يمكننا تعديل المتغير بالمرور. دعم كلتا الطريقتين؟

شكرًا.

هل كانت مفيدة؟

المحلول

هناك فرق بين الاثنين. النظر في ما يلي:

#include <iostream>
#include <string>
using std::string;

string g_value;

void callback() {
    g_value = "blue";
}

void ProcessStringByRef(const string &s) {
    callback();
    std::cout << s << "\n";
}

void ProcessStringByValue(const string s) {
    callback();
    std::cout << s << "\n";
}

int main() {
    g_value = "red";
    ProcessStringByValue(g_value);
    g_value = "red";
    ProcessStringByRef(g_value);
}

انتاج:

red
blue

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

نظرًا لأنهم يقومون بأشياء مختلفة ، يتيح لك C ++ اختيار ما تريد.

هناك عواقب على الأداء في كلتا الحالتين - عندما تمر حسب القيمة ، يجب إجراء نسخة ، والتي تكاليفها. لكن المترجم يعلم بعد ذلك أن وظيفتك فقط يمكن أن يكون لها أي إشارات إلى تلك النسخة ، والتي قد تسمح بتحسينات أخرى. لا يمكن لـ ProcessStringByref تحميل محتويات السلسلة للطباعة حتى callback() لقد عاد. يمكن لـ ProcessStringByValue ، إذا كان المترجم يعتقد أن القيام بذلك أسرع.

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

نصائح أخرى

f(const string&) يأخذ سلسلة من قبل const المرجعي: fيعمل مباشرة على كائن السلسلة الذي تم تمريره بالرجوع إليه: لا توجد نسخة متورطة. const يمنع التعديلات على الكائن الأصلي على الرغم من.

f(const string) يأخذ قيمة السلسلة ، مما يعني f يتم إعطاء نسخة من السلسلة الأصلية. حتى لو كنت تسقط const, ، عند المرور بالقيمة ، يتم فقدان أي تعديل للسلسلة عندما f عائدات.

لا أعرف بالضبط ما تعنيه بـ "لماذا تدعم C ++ كلتا الطريقتين؟". إنها مجرد قواعد عامة على الحمل الزائد.

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

في f(const string& s) بدلاً من ذلك ، لا يتم نسخ السلسلة ولكنك تمرر إليها. عادة ما يتم ذلك عندما يكون لديك كائن كبير بحيث يمكن لـ "التمرير بالقيمة" إنتاج علوي (لهذا السبب يدعم C ++ كلا الطريقتين). يعني التمرير بالرجوع أنه يمكنك تغيير قيمة الكائن "الكبير" الذي تمرره ، ولكن بسبب محدد const ، لا يمكنك تعديله. إنه نوع من "الحماية".

يمكن أن يحتوي كائنك على ملف متقلب العضو ، الذي يمكن تعديله حتى مع مرجع const

مع outputStringByRef يجب عليك التأكد outputStringByRef يحتاج ذلك. مع outputStringByVal يمكن أن يموت المتغير الذي مررت به أو الخروج من النطاق والنسخة التي لديها الوظيفة لا تزال على ما يرام.

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

في الواقع const mystring &ss شكل استطاع تغيير السلسلة ، إذا أ const_cast<mystring&> تم استخدامه - على الرغم من أنني لا أوصي بذلك هنا.

تخيل أنك تريد طباعة كائن لا يمكن نسخه:

Thread th;
// ...
cout << th; // print out informations...

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

قد يساعدك على التفكير في انسخ كائن كما استنساخ كائن. نسخ th أعلاه في C ++ لا يعني فقط أن يكون لديك مقبض آخر لنفس الكائن كما هو الحال في Java - وهذا يعني أن يستنسخ الكائن بشكل ضمني ، والحصول على نسخة كاملة منه. لذا فإن السؤال مشابه لـ "لماذا تدعم Java كليهما Object.clone ونسخ المراجع؟ " - كلاهما لهما أغراض مختلفة.

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top