c++ - شرح - using namespace std معنى




لماذا يعتبر استخدام "مساحة الاسم القياسية" ممارسة سيئة؟ (20)

لقد قيل لي من قبل الآخرين أن الكتابة using namespace std في using namespace std في الكود غير صحيح ، وأنه يجب استخدام std::cout و std::cin بدلاً من ذلك.

لماذا يعتبر using namespace std ممارسة سيئة؟ هل هو غير فعال أم أنه يخاطر بالتعبير عن المتغيرات الملتبسة (المتغيرات التي تشترك في الاسم نفسه كدالة في مساحة الاسم ( std )؟ هل يؤثر على الأداء؟


لا تستخدمها على مستوى العالم

تعتبر "سيئة" فقط عند استخدامها على مستوى العالم . لان:

  • أنت تشغل مساحة الاسم التي تقوم ببرمجتها.
  • سيجد القراء صعوبة في معرفة من أين يأتي معرف معين ، عند استخدام الكثيرين using namespace xyz .
  • كل ما هو صحيح بالنسبة للقراء الآخرين من شفرة المصدر الخاص بك هو أكثر صحة للقارئ الأكثر شيوعا من ذلك: نفسك. أعود بعد سنة أو سنتين وألقِ نظرة ...
  • إذا كنت تتحدث فقط عن using namespace std قد لا تكون على دراية بكل الأشياء التي تلتقطها - وعندما تقوم بإضافة #include أخرى أو الانتقال إلى مراجعة جديدة لـ C ++ قد تحصل على تعارضات الأسماء التي لم تكن على دراية بها.

يمكنك استخدامه محليا

المضي قدما واستخدامها محليا (تقريبا) بحرية. هذا ، بالطبع ، يمنعك من تكرار std:: - والتكرار هو أيضا سيئة.

لغة لاستخدامها محليا

في C ++ 03 كان هناك كود اصطلاحي - بلغة معيارية - لتنفيذ وظيفة swap لصفوفك. تم اقتراح أنك تستخدم بالفعل using namespace std محلية using namespace std - أو على الأقل using std::swap :

class Thing {
    int    value_;
    Child  child_;
public:
    // ...
    friend void swap(Thing &a, Thing &b);
};
void swap(Thing &a, Thing &b) {
    using namespace std;      // make `std::swap` available
    // swap all members
    swap(a.value_, b.value_); // `std::stwap(int, int)`
    swap(a.child_, b.child_); // `swap(Child&,Child&)` or `std::swap(...)`
}

هذا يفعل السحر التالي:

  • سيختار المحول البرمجي std::swap for value_ ، أي void std::swap(int, int) .
  • إذا كان لديك void swap(Child&, Child&) الزائد void swap(Child&, Child&) ، سيقوم المترجم باختياره.
  • إذا لم يكن لديك هذا التحميل الزائد ، سيستخدم المترجم void std::swap(Child&,Child&) وجرب مبادلة أفضلها.

مع C ++ 11 لا يوجد سبب لاستخدام هذا النمط بعد الآن. تم تغيير تطبيق std::swap للبحث عن الحمل الزائد المحتمل واختياره.


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

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


أنا أيضا نعتبرها ممارسة سيئة. لماذا ا؟ يوم واحد فقط اعتقدت أن وظيفة مساحة الاسم هي لتقسيم الأشياء ، لذا لا ينبغي لي أن أفسدها مع رمي كل شيء في كيس عالمي واحد. ومع ذلك ، إذا كنت غالبا ما أستخدم "cout" و "cin" ، فإنني أكتب: using std::cout; using std::cin; using std::cout; using std::cin; في ملف cpp (أبداً في ملف الرأس لأنه ينتشر مع #include ). أعتقد أن أي شخص عاقل سوف يطلق اسمًا على cout أو cin . ؛)


أنا ركض مؤخرا إلى شكوى حول Visual Studio 2010 . اتضح أن كل الملفات المصدر كانت تحتوي على هذين السطرين:

using namespace std;
using namespace boost;

وهناك الكثير من ميزات Boost في معيار C ++ 0x ، و Visual Studio 2010 يحتوي على الكثير من ميزات C ++ 0x ، فجأة لم يتم تجميع هذه البرامج.

لذلك ، تجنب using namespace X; هو شكل من أشكال التدقيق المستقبلي ، وهي طريقة للتأكد من أن التغيير في المكتبات و / أو ملفات الرأس المستخدمة لا يؤدي إلى كسر أحد البرامج.


إذا قمت باستيراد ملفات رأس الصفحة الصحيحة ، فستحصل فجأة على أسماء مثل hex أو left أو plus أو count في count العالمي. قد يكون هذا مفاجئًا إذا لم تكن على علم بأن std:: يحتوي على هذه الأسماء. إذا كنت تحاول أيضًا استخدام هذه الأسماء محليًا ، فقد يؤدي ذلك إلى بعض الارتباك.

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


إصدار قصير: لا تستخدم العمومية باستخدام الإعلانات أو التوجيهات في ملفات الرأس. لا تتردد في استخدامها في ملفات التنفيذ. هنا ما يجب على Herb Sutter و Andrei Alexandrescu قوله عن هذه المسألة في C ++ Coding Standards (bold for emphasis is mine):

ملخص

إن استخدامات مساحة الاسم هي من أجل راحتك ، وليس لك لإلحاقها بالآخرين: لا تقم أبدًا بكتابة تصريح استخدام أو توجيه باستخدام أمر قبل التوجيه #include.

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

نقاش

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


المشكلة مع using namespace في ملفات رأس الفئات الخاصة بك هو أنه يفرض على أي شخص يريد استخدام الفئات الخاصة بك (بتضمين ملفات الرأس الخاصة بك) أن يكون أيضًا 'استخدام' (أي رؤية كل شيء في) مساحات الأسماء الأخرى.

ومع ذلك ، قد لا تتردد في وضع عبارة استخدام في ملفات (.) * .cpp الخاصة بك.

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

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

تقترح الأسئلة الشائعة بديلين:

  • إعلان استخدام:

    using std::cout; // a using-declaration lets you use cout without qualification
    cout << "Values:";
    
  • مجرد كتابة الأمراض المنقولة جنسيا ::

    std::cout << "Values:";
    

سبب آخر هو مفاجأة.

إذا رأيت cout << blah ، بدلاً من std::cout << blah

أعتقد ما هو هذا cout ؟ هل هو cout العادي؟ هل هو شيء مميز؟


من الجيد رؤية الرمز ومعرفة ما يفعله. إذا رأيت std::cout أعلم أن دفق cout للمكتبة std . إذا رأيت cout فأنا لا أعرف. يمكن أن يكون دفق cout للمكتبة std . أو يمكن أن يكون هناك int cout = 0; عشرة خطوط أعلى في نفس الوظيفة. أو متغير static اسمه cout في هذا الملف. يمكن أن يكون أي شيء.

الآن ، احصل على مليون رمز سطر ، وهو ليس كبيرًا بشكل خاص ، وكنت تبحث عن خطأ ، مما يعني أنك تعرف أن هناك سطرًا واحدًا في هذه المليون سطر لا يفعل ما يفترض القيام به. cout << 1; يمكن قراءة static int المسماة cout ، وتحولها إلى اليسار من جانب واحد ، ورمي النتيجة. تبحث عن علة ، فما استقاموا لكم فاستقيموا للتحقق من ذلك. هل يمكنك أن ترى كيف أفضّل حقاً رؤية std::cout ؟

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


هذا لا يرتبط بالأداء على الإطلاق. ولكن ضع في اعتبارك ذلك: أنت تستخدم مكتبتين تدعى Foo and Bar:

using namespace foo;
using namespace bar;

كل شيء يعمل بشكل جيد ، يمكنك استدعاء Blah() من Foo و Quux() من شريط دون مشاكل. ولكن في يوم من الأيام تقوم بالترقية إلى إصدار جديد من Foo 2.0 ، والذي يقدم الآن وظيفة تسمى Quux() . الآن لديك مشكلة: كل من Foo 2.0 و Bar import Quux() في مساحة الاسم العالمية الخاصة بك. هذا سوف يستغرق بعض الجهد لإصلاح ، لا سيما إذا كانت المعلمات الدالة تتطابق.

إذا كنت قد استخدمت foo::Blah() و bar::Quux() ، فستكون مقدمة foo::Quux() بدون حدث.


يستخدم المبرمجون ذوو الخبرة ما يحلون مشاكلهم ويتجنبون ما يخلق مشاكل جديدة ، ويتفادون توجيهات استخدام مستوى الرأس لهذا السبب بالتحديد.

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

ما هي هذه الأسباب الجيدة ؟ في بعض الأحيان ، يرغب المبرمجون بوضوح في إيقاف تشغيل ADL ، وأحيانًا أخرى يريدون إلغاء الغموض.

ما يلي على ما يرام:

  1. توجيهات استخدام على مستوى الوظيفة واستخدام إعلانات - داخل تطبيقات الوظائف
  2. مصدر - على مستوى الملف - الإعلانات داخل الملفات المصدر
  3. (في بعض الأحيان) التوجيهات باستخدام مستوى الملف المصدر

أنا أتفق مع الآخرين هنا ، لكني أود أن أتطرق إلى المخاوف المتعلقة بقابلية القراءة - يمكنك تجنب كل ذلك ببساطة عن طريق استخدام الرموز المطبوعة في أعلى ملفك أو وظيفتك أو إعلان الفصل.

وعادة ما أستخدمه في إعلان الفئة الخاصة بي حيث تميل الأساليب في الفصل إلى التعامل مع أنواع البيانات المشابهة (الأعضاء) و typedef فرصة لتعيين اسم ذي معنى في سياق الفصل الدراسي. هذا في الواقع يساعد على سهولة القراءة في تعريفات طرق الصف.

//header
class File
{
   typedef std::vector<std::string> Lines;
   Lines ReadLines();
}

وفي التنفيذ:

//cpp
Lines File::ReadLines()
{
    Lines lines;
    //get them...
    return lines;
}

في مقابل:

//cpp
vector<string> File::ReadLines()
{
    vector<string> lines;
    //get them...
    return lines;
}

أو:

//cpp
std::vector<std::string> File::ReadLines()
{
    std::vector<std::string> lines;
    //get them...
    return lines;
}

يعتبر

// myHeader.h
#include <sstream>
using namespace std;


// someoneElses.cpp/h
#include "myHeader.h"

class stringstream {  // uh oh
};

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

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


"لماذا" باستخدام مساحة الاسم القياسية ؛ يعتبر ممارسة سيئة في C ++؟ "

أضعه في الاتجاه الآخر: لماذا تعتبر الكتابة 5 أحرف إضافية مرهقة من قبل البعض؟

فكر مثلاً في كتابة جزء من برنامج عددي ، فلماذا أفكر حتى في تلويث مساحيتي العالمية عن طريق قطع "std :: vector" بشكل عام إلى "vector" عندما يكون "vector" أحد أهم مفاهيم نطاق المشكلة؟


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


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


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

أعني حقاً ... أقول "لا تعتمد على هذا الوجود" هو مجرد إعدادك للاعتماد عليه غير موجود. ستواجه دائمًا مشكلات في استعارة مقتطفات الشفرة وإصلاحها باستمرار. فقط احتفظ بأشياء محددة من قبل المستخدم واقترحت في نطاق محدود كما ينبغي وينبغي أن تكون متحمسة للغاية مع globals (بصدق globals ينبغي أن يكون دائما الملاذ الأخير لأغراض "تجميع الآن ، التعقل في وقت لاحق"). حقا أعتقد أنها نصيحة سيئة من معلمك لأن استخدام std سيعمل لكل من "cout" و "std :: cout" ولكن NOT ستعمل باستخدام std ستعمل فقط لـ "std :: cout". لن تكون دائمًا محظوظًا بما يكفي لكتابة جميع الرموز الخاصة بك.

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


مثال ملموس لتوضيح القلق. تخيل أن لديك موقفًا يحتوي على مكتبتين ، foo و bar ، كل منهما بهما مساحة الاسم الخاصة بهما:

namespace foo {
    void a(float) { /* does something */ }
}

namespace bar {
    ...
}

الآن لنفترض أنك تستخدم foo وشريطًا معًا في برنامجك الخاص على النحو التالي:

using namespace foo;
using namespace bar;

void main() {
    a(42);
}

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

namespace bar {
    void a(float) { /* does something completely different */ }
}

عند هذه النقطة ستحصل على خطأ في برنامج التحويل البرمجي:

using namespace foo;
using namespace bar;

void main() {
    a(42);  // error: call to 'a' is ambiguous, should be foo::a(42)
}

لذلك ستحتاج إلى إجراء بعض الصيانة لتوضيح "أ" تعني (أي foo::a). هذا ربما غير مرغوب فيه ، ولكن لحسن الحظ أنه من السهل جدا (فقط إضافة foo::أمام جميع المكالمات إلى aأن علامات المجمع على أنها غامضة).

ولكن تخيل سيناريو بديل حيث تغير الشريط بدلاً من ذلك ليبدو هكذا:

namespace bar {
    void a(int) { /* does something completely different */ }
}

عند هذه النقطة دعوتكم ل a(42)يربط فجأة bar::aبدلا من foo::aوبدلا من القيام 'شيء' يفعل "شيئا مختلفا تماما. لا تحذير المترجم أو أي شيء. يبدأ برنامجك بصمت في عمل شيء مختلف تمامًا عن ذي قبل.

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

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


من خبراتي ، إذا كانت لديك مكتبات متعددة تستخدمها cout، لكن لغرض مختلف ، فقد تستخدم الخطأ cout.

على سبيل المثال، إذا كنت اكتب في، using namespace std;و using namespace otherlib;واكتب فقط cout (الذي صادف أن يكون في كليهما)، بدلا من std::cout(أو 'otherlib::cout')، قد تستخدم خطأ واحد، والحصول على الأخطاء، هو أكثر فعالية بكثير وكفاءة استخدام std::cout.


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





c++-faq