سؤال

ما هو جيد القائمة فئة/تصميم نمط متعدد مرحلة البناء/تهيئة كائن في C++?

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

حاليا أنا أستخدم دفعة::اختياري عن تأخر البناء من أعضاء البيانات, لكنه يزعجني أن اختياري هو لغويا مختلفة من تأخير شيدت.

ما أريد تذكر ملامح دفعة::ربط امدا جزئية وظيفة التطبيق باستخدام هذه المكتبات يمكنني تصميم متعدد المراحل البناء - ولكن يفضل استخدام القائمة, اختبار الطبقات.(أو ربما هناك آخر متعدد مرحلة البناء النمط الذي أنا لست على دراية).

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

المحلول

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

أنواع مجموعة المعلمة

إذا قمت بتمييز الكائنات المكتظة بالسكان بالكامل عن الكائنات غير المكتظة بالسكان على مستوى النوع ، فيمكنك فرض شرط تمرير وظيفة كائن كامل. للقيام بذلك ، أود أن أقترح إنشاء نوع مقابل XParams لكل نوع ذي صلة X. XParams لديها boost::optional أعضاء ووظائف setter لكل معلمة يمكن تعيينها بعد البناء الأولي. ثم يمكنك فرض X للحصول على مُنشئ واحد (غير نسخة) ، يستغرق XParams كوسيطة وحيدها والتحقق من أن كل معلمة ضرورية قد تم تعيينها داخل ذلك XParams هدف. (لست متأكدًا مما إذا كان لهذا النمط اسم - أي شخص يحب تحرير هذا لملءنا؟)

أنواع "الكائن الجزئي"

هذا يعمل بشكل رائع إذا لم يكن لديك حقًا فعل أي شيء يحتوي على الكائن قبل أن يتم ملؤه بالكامل (ربما بخلاف الأشياء التافهة مثل استعادة قيم الحقل). إذا كان عليك في بعض الأحيان التعامل مع السكان بشكل غير مكتمل X مثل "ممتلئ" X, ، يمكنك بدلاً من ذلك القيام به X اشتق من نوع XPartial, الذي يحتوي على كل المنطق ، بالإضافة إلى protected الطرق الافتراضية لإجراء اختبارات الشروط المسبقة التي تختبر ما إذا كانت جميع الحقول الضرورية ملأمة. ثم إذا X يضمن أنه لا يمكن بناؤه إلا في حالة محظورة بالكامل ، يمكن أن يتجاوز تلك الطرق المحمية بالشيكات التافهة التي تعود دائمًا true:

class XPartial {
    optional<string> name_;

public:
    void setName(string x) { name_.reset(x); }  // Can add getters and/or ctors
    string makeGreeting(string title) {
        if (checkMakeGreeting_()) {             // Is it safe?
            return string("Hello, ") + title + " " + *name_;
        } else {
            throw domain_error("ZOINKS");       // Or similar
        }
    }
    bool isComplete() const { return checkMakeGreeting_(); }  // All tests here

protected:
    virtual bool checkMakeGreeting_() const { return name_; }   // Populated?
};

class X : public XPartial {
    X();     // Forbid default-construction; or, you could supply a "full" ctor

public:
    explicit X(XPartial const& x) : XPartial(x) {  // Avoid implicit conversion
        if (!x.isComplete()) throw domain_error("ZOINKS");
    }

    X& operator=(XPartial const& x) {
        if (!x.isComplete()) throw domain_error("ZOINKS");
        return static_cast<X&>(XPartial::operator=(x));
    }

protected:
    virtual bool checkMakeGreeting_() { return true; }   // No checking needed!
};

على الرغم من أنه قد يبدو أن الميراث هنا هو "العودة إلى الأمام" ، فإن القيام بذلك بهذه الطريقة يعني أن X يمكن تزويدها بأمان في أي مكان XPartial& يطلب ، لذلك هذا النهج يطيع مبدأ استبدال Liskov. هذا يعني أن الوظيفة يمكن أن تستخدم نوعًا من المعلمة X& للإشارة إلى أنه يحتاج إلى كامل X كائن ، أو XPartial& للإشارة إلى أنه يمكن التعامل مع الكائنات المأهولة جزئيًا - وفي هذه الحالة إما XPartial كائن أو ممتلئ X يمكن تمريرها.

في الأصل كان لدي isComplete() مثل protected, ، ولكن وجدت أن هذا لم ينجح منذ ذلك الحين Xيجب أن يتصل بنسخ CTOR ومشغل المهام هذه الوظيفة على XPartial& الحجة ، وليس لديهم وصول كاف. عند التفكير ، من المنطقي أن تكشف هذه الوظيفة علنًا.

نصائح أخرى

يجب أن أفتقد شيئًا هنا - أفعل هذا النوع من الأشياء طوال الوقت. من الشائع جدًا أن يكون لديك كائنات كبيرة و/أو لا يحتاجها فئة في جميع الظروف. لذلك أنشئها ديناميكيا!

struct Big {
    char a[1000000];
};

class A {
  public: 
    A() : big(0) {}
   ~A() { delete big; }

   void f() {
      makebig();
      big->a[42] = 66;
   }
  private:
    Big * big;
    void makebig() {
      if ( ! big ) {
         big = new Big;
      }
    }
};

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

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

وأود أن استخدام مؤشرات لهذه الأعضاء ، وعندما تحتاج إلى أن تبنى ونقلها في نفس الوقت.يمكنك استخدام auto_ptr هذه ، وتحقق ضد NULL لمعرفة ما إذا كانت تهيئة.(أعتقد من مؤشرات المدمج في "اختياري" اكتب في C/C++/Java, وهناك لغات أخرى حيث فارغة ليست صالحة المؤشر).

واحد المسألة على النمط هو أنه قد يتم الاعتماد على منشئات أن تفعل الكثير من العمل.عندما أنا الترميز OO, علي منشئات هل يكفي العمل على الحصول على كائن في حالة متناسقة.على سبيل المثال إذا كنت Image فئة لا تريد أن تقرأ من ملف يمكنني أن أفعل هذا:

image = new Image("unicorn.jpeg"); /* I'm not fond of this style */

أو أنا يمكن أن تفعل هذا:

image = new Image(); /* I like this better */
image->read("unicorn.jpeg");

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

وأود أن أقول أكثر ولكن أنا لا أعرف ما كنت تحاول القيام به مع تأخر البناء.

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

class Counter {
public:
    Counter(int &cref) : c(cref) { }
    void incr(int x) { c += x; }
private:
    int &c;
};

void dontTryThisAtHome() {
    int i = 0, j = 0;
    Counter c(i);       // Call constructor first time on c
    c.incr(5);          // now i = 5
    new(&c) Counter(j); // Call the constructor AGAIN on c
    c.incr(3);          // now j = 3
}

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

باستخدام Boost.Optional يشبه الحل الجيد لبعض حالات الاستخدام. لم ألعب كثيرًا معها ، لذا لا يمكنني التعليق كثيرًا. شيء واحد أضعه في الاعتبار عند التعامل مع هذه الوظيفة هو ما إذا كان بإمكاني استخدام المُنشئين المحملين بدلاً من المُنشئين الافتراضي والنسخ.

عندما أحتاج إلى هذه الوظيفة ، سأستخدم مؤشرًا لنوع الحقل اللازم مثل هذا:

public:
  MyClass() : field_(0) { } // constructor, additional initializers and code omitted
  ~MyClass() {
    if (field_)
      delete field_; // free the constructed object only if initialized
  }
  ...
private:
  ...
  field_type* field_;

بعد ذلك ، بدلاً من استخدام المؤشر ، سأصل إلى الحقل من خلال الطريقة التالية:

private:
  ...
  field_type& field() {
    if (!field_)
      field_ = new field_type(...);
    return field_;
  }

لقد حذفت دلالات الوصول

إن أسهل طريقة أعرفها تشبه التقنية التي اقترحها ديتريش إيب ، إلا أنها تتيح لك تأخير بناء كائن حتى لحظة اختيارك.

بشكل أساسي: احجز الكائن باستخدام Malloc بدلاً من الجديد (وبالتالي تجاوز المُنشئ) ، ثم اتصل بالمشغل الجديد المفرط عندك عندك حقا تريد بناء الكائن عبر وضع جديد.

مثال:

Object *x = (Object *) malloc(sizeof(Object));
//Use the object member items here. Be careful: no constructors have been called!
//This means you can assign values to ints, structs, etc... but nested objects can wreak havoc!

//Now we want to call the constructor of the object
new(x) Object(params);

//However, you must remember to also manually call the destructor!
x.~Object();
free(x);

//Note: if you're the malloc and new calls in your development stack 
//store in the same heap, you can just call delete(x) instead of the 
//destructor followed by free, but the above is the  correct way of 
//doing it

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

لا أعرف ما إذا كان هناك نمط رسمي لهذا الغرض. في الأماكن التي رأيتها فيها ، أطلقنا عليها اسم "Lazy" أو "الطلب" أو "عند الطلب".

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