سؤال

هل من الممارسات الجيدة أن يكون لديك مُنشئ فئة يستخدم المعلمات الافتراضية ، أم يجب أن أستخدم منشئات منفصلة محملة؟ فمثلا:

// Use this...
class foo  
{
private:
    std::string name_;
    unsigned int age_;
public:
    foo(const std::string& name = "", const unsigned int age = 0) :
        name_(name),
        age_(age)
    {
        ...
    }
};

// Or this?
class foo  
{
private:
    std::string name_;
    unsigned int age_;
public:
    foo() :
    name_(""),
    age_(0)
{
}

foo(const std::string& name, const unsigned int age) :
        name_(name),
        age_(age)
    {
        ...
    }
};

يبدو أن كلا الإصدارين يعملون ، على سبيل المثال:

foo f1;
foo f2("Name", 30);

ما هو النمط الذي تفضله أو توصي به ولماذا؟

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

المحلول

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

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

نصائح أخرى

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

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

class Vehicle {
public:
  Vehicle(int wheels, std::string name = "Mini");
};

Vehicle x = 5;  // this compiles just fine... did you really want it to?

تنطبق هذه المناقشة على كل من البنائين ، ولكن أيضًا الأساليب والوظائف.

باستخدام المعلمات الافتراضية؟

الشيء الجيد هو أنك لن تحتاج إلى زيادة التحميل/الأساليب/الوظائف لكل حالة:

// Header
void doSomething(int i = 25) ;

// Source
void doSomething(int i)
{
   // Do something with i
}

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

إذا لم تقم بذلك ، فستظل المصادر تستخدم القيمة الافتراضية القديمة.

باستخدام منشئات/طرق/وظائف محملة؟

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

// Header
void doSomething() ;
void doSomething(int i) ;

// Source

void doSomething()
{
   doSomething(25) ;
}

void doSomething(int i)
{
   // Do something with i
}

المشكلة هي أنه يتعين عليك الحفاظ على مُنشأة/طرق/وظائف متعددة ، وموجهاتها.

في تجربتي ، تبدو المعلمات الافتراضية رائعة في ذلك الوقت وجعل عامل الكسل الخاص بي سعيدًا ، ولكن بعد ذلك ، أستخدم الفصل وأنا مندهش عندما يبدأ الافتراضي. لذلك لا أعتقد حقًا أنها فكرة جيدة ؛ من الأفضل أن يكون لديك اسم classname :: classname () ثم اسم classname :: init (Arglist). فقط لتلك الحافة الصيفية.

سام تعطي الإجابة السبب في أن الوسيطات الافتراضية مفضلة للمنتدين بدلاً من التحميل الزائد. أريد فقط أن أضيف أن C ++-0x سيسمح وفد من مُنشئ إلى آخر ، وبالتالي إزالة الحاجة إلى التخلف عن السداد.

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

class Thingy2
{
public:
    enum Color{red,gree,blue};
    Thingy2();

    Thingy2 & color(Color);
    Color color()const;

    Thingy2 & length(double);
    double length()const;
    Thingy2 & width(double);
    double width()const;
    Thingy2 & height(double);
    double height()const;

    Thingy2 & rotationX(double);
    double rotationX()const;
    Thingy2 & rotatationY(double);
    double rotatationY()const;
    Thingy2 & rotationZ(double);
    double rotationZ()const;
}

main()
{
    // gets default rotations
    Thingy2 * foo=new Thingy2().color(ret)
        .length(1).width(4).height(9)
    // gets default color and sizes
    Thingy2 * bar=new Thingy2()
        .rotationX(0.0).rotationY(PI),rotationZ(0.5*PI);
    // everything specified.
    Thingy2 * thing=new Thingy2().color(ret)
        .length(1).width(4).height(9)
        .rotationX(0.0).rotationY(PI),rotationZ(0.5*PI);
}

الآن عند إنشاء الكائنات ، يمكنك اختيار اختيار الخصائص التي يجب تجاوزها وأي تلك التي قمت بتعيينها بشكل صريح. أكثر قابلية للقراءة :)

أيضًا ، لم يعد عليك تذكر ترتيب الحجج إلى المنشئ.

شيء آخر يجب مراعاته هو ما إذا كان يمكن استخدام الفصل في صفيف:

foo bar[400];

في هذا السيناريو ، لا توجد ميزة لاستخدام المعلمة الافتراضية.

هذا بالتأكيد لن يعمل:

foo bar("david", 34)[400]; // NOPE

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

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

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

أيضا ، تجدر الإشارة إلى أن دليل نمط Google C ++ يتجنب كل من حجج CTOR (ما لم يكن ضروريًا للغاية) ، و الوسيطات الافتراضية للوظائف أو الأساليب.

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

شيء واحد يزعجني بالمعلمات الافتراضية هو أنه لا يمكنك تحديد المعلمات الأخيرة ولكن استخدام القيم الافتراضية للذات الأولى. على سبيل المثال ، في التعليمات البرمجية الخاصة بك ، لا يمكنك إنشاء FOO بدون اسم سوى عمر معين (ومع ذلك ، إذا كنت أتذكر بشكل صحيح ، فسيكون ذلك ممكنًا في C ++ 0x ، مع بناء جملة البناء الموحد). في بعض الأحيان ، هذا أمر منطقي ، ولكن يمكن أن يكون محرجًا حقًا.

في رأيي ، لا توجد قاعدة. Personnaly ، أميل إلى استخدام مُنشئات متعددة (أو طرق) ، إلا إذا كانت الوسيطة الأخيرة تحتاج فقط إلى قيمة افتراضية.

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

مثال:

يمكنك استخدام التحميل الزائد لكتابة (int x و foo & a) و (int x) ، ولكن لا يمكنك استخدام المعلمة الافتراضية لكتابة (int x ، foo & = null).

القاعدة العامة هي استخدام كل ما هو منطقي ويجعل الكود أكثر قابلية للقراءة.

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

أنا شخصياً أحب الإعدادات الافتراضية عند الاقتضاء ، لأنني لا أحب الرمز المتكرر. ymmv.

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