Ленивая конструкция / конструкция мультиэтапа в C++

Что такое хороший существующий класс/шаблон разработки для многоступенчатой конструкции/инициализации объекта в C++?

У меня есть класс с некоторыми элементами данных, которые должны быть инициализированы в различных точках в потоке программы, таким образом, их инициализация должна быть задержана. Например, один аргумент может быть считан из файла и другого из сети.

В настоящее время я использую повышение:: дополнительный для отложенной конструкции элементов данных, но это беспокоит меня, который дополнительный семантически отличается, чем созданный из задержки.

То, в чем я нуждаюсь, напоминает функциям о повышении:: свяжите и лямбда частичное функциональное приложение, и пользующийся этими библиотеками, я могу, вероятно, разработать многоступенчатую конструкцию - но я предпочитаю использовать существующие, протестированные классы. (Или возможно существует другой многоступенчатый шаблон конструкции, который я не знаком с).

7
задан sth 29 July 2010 в 08:03
поделиться

6 ответов

Ключевой проблемой заключается в том, следует ли вы различать полностью населенные объекты из не полностью населенных объектов на уровне типа . Если вы решите не сделать различие, то просто используйте BOOST :: Дополнительно или похожие, как вы делаете: это позволяет легко получить кодирование. OTOH Вы не можете получить компилятор, чтобы обеспечить требование о том, что конкретная функция требует полностью населенного объекта; Вам необходимо выполнить проверку выполнения полей каждый раз.

Типы групп параметров

Если вы различите полностью населенные объекты из не полностью заселенных объектов на уровне типа, вы можете обеспечить требование, чтобы функция была передана полным объектом. Для этого я бы предложил создать соответствующий тип xparams для каждого соответствия x . XParams имеет BOOST :: Дополнительные элементы и настройки для каждого параметра, которые можно установить после начальной конструкции. Тогда вы можете принудительно x , чтобы иметь только один (не копируемый) конструктор, который требует xparams в качестве его единственного аргумента и проверяет, что каждый необходимый параметр был установлен внутри этого Xparams объект. (Не уверена, что имеет ли этот шаблон именем - никто нравится редактировать это, чтобы заполнить нас?)

Типы «Частичные объекты»

Это работает чудесно, если вам не нужно Что-нибудь с объектом, прежде чем он полностью заполнен (возможно, кроме тривиальных вещей, таких как получение значений поля). Если вам нужно иногда относиться к не полностью заполнено x , как «полный» x , вы можете вместо этого сделать x , полученный из типа xpartial , который содержит все логику, плюс защищенные виртуальные методы для выполнения предварительных тестов, которые проверяют, заполнены ли все необходимые поля. Тогда, если 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 & , так что этот подход подходит Принцип замены Лискива . Это означает, что функция может использовать тип параметра x & , чтобы указать, что ему нужен полный объект x , или xpartial & , чтобы указать, что он может обрабатывать частично населенные объекты - - В этом случае можно пропустить либо xpartial объект или полный x .

Первоначально я имел ISCOMPLETE () как защищено , но нашел это не сработало с x копия CTOR и оператор назначения должен вызывать эту функцию На их аргументе Xpartial & , и у них нет достаточного доступа. Об отражении, это имеет больше смысла публико разоблачить эту функциональность.

4
ответ дан 7 December 2019 в 12:20
поделиться

Я не знаю, есть ли формальный шаблон для этого. В местах, где я его видел, мы назвали это «ленивым», «спросом» или «по требованию».

0
ответ дан 7 December 2019 в 12:20
поделиться

Использование 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_;
  }

Я пропущен семантика доступа Const

0
ответ дан 7 December 2019 в 12:20
поделиться

Я не знаю никаких моделей для решения этой конкретной проблемы. Это сложный дизайн вопроса, и один несколько уникальный для языков, таких как C ++. Другой проблемой состоит в том, что ответ на этот вопрос тесно связан с вашим индивидуальным (или корпоративным) стилем кодирования.

Я бы использовал указатели для этих членов, и когда они должны быть построены, выделите их одновременно. Вы можете использовать AUTO_PTR для них и проверять против NULL, чтобы увидеть, инициализируются ли они. (Я думаю, что указатели представляют собой встроенный «дополнительный» тип в C / C ++ / Java, есть другие языки, где NULL не является действительным указателем).

Одной из проблем в стиле является то, что вы можете полагаться на своих конструкторов, чтобы сделать слишком много работы. Когда я кодирую oO, у меня есть конструкторы, достаточно работа, чтобы получить объект в согласованном состоянии. Например, если у меня есть изображение класса , и я хочу прочитать из файла, я мог бы сделать это:

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
}

Обратите внимание, что делает что-то безрассудное, так как это может заработать вам презрение ваших товарищей по программистам, если только у вас есть солидные причины использования этой техники. Это также не задерживает конструктор, просто позволяет вам позвонить позже.

0
ответ дан 7 December 2019 в 12:20
поделиться

Самый простой способ, который я знаю, похож на технику, предложенную Дитрихом Эппом, за исключением того, что он позволяет по-настоящему отложить строительство объекта до момента выбора.

В принципе: резервируйте объект с помощью malloc вместо new (тем самым обходя конструктор), затем вызывайте перегруженный оператор new, когда вы действительно захотите построить объект с помощью размещения new.

Пример:

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++. Как предлагает Дитрих, вы должны спросить, действительно ли вы должны задерживать вызов конструктора. Конструктор base должен выполнять минимум, чтобы привести ваш объект в работоспособное состояние, в то время как другие перегруженные конструкторы могут выполнять больше работы по мере необходимости.

0
ответ дан 7 December 2019 в 12:20
поделиться

Я должен пропустить что-то здесь - я делаю такого рода вещей все время. Очень распространено, чтобы иметь объекты, которые большие и / или не нуждаются в классе при любых обстоятельствах. Так что создайте их динамически!

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 (и, возможно, встроенный), а большой указатель, вероятно, должен быть изменен. И, конечно, должен быть в состоянии построить большую, что может в других случаях, что означает кэширование параметров конструктора класса класса. Вам также нужно будет определиться с политикой копирования / назначения - я бы, вероятно, запретил как для такого класса.

1
ответ дан 7 December 2019 в 12:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: