Избегайте вызова конструктора переменной-члена

У меня есть следующий С++-класс:

// Header-File
class A
{
    public:
    A();

    private:
    B m_B;
    C m_C;
};

// cpp-File
A::A()
: m_B(1)
{
    m_B.doSomething();
    m_B.doMore();
    m_C = C(m_B.getSomeValue());
}

Теперь я хотел бы избежать class A вызова любого конструктора C m_C. Потому что в последней строке в A::A() я все равно сам инициализирую m_C, потому что мне нужно сначала подготовить m_B. Я мог бы предоставить пустой конструктор по умолчанию для class B. Но это не идея.

Я уже пытался добавить m_C(NULL) в список инициализации A::A(). Иногда это срабатывало, иногда сообщалось, что нет конструктора, принимающего NULL в качестве аргумента.

Итак, как я могу оставить m_C неинициализированным? Я знаю, что с указателями работает способ m_C(NULL). И я не хочу выделять его динамически, используя new.

Любая идея приветствуется.


person Atmocreations    schedule 20.10.2011    source источник
comment
Злой способ - иметь char m_C_data[sizeof(C)] в качестве члена, а затем делать в него новое размещение при инициализации. Это позволит избежать выделения кучи, но вам придется приводить ее к правильному типу каждый раз, когда вы ее используете. Конечно, следует помнить о сложности типа C (это POD, имеет vftable, «еще больше вещей, о которых я не знаю» и т. д.).   -  person Akanksh    schedule 20.10.2011
comment
Есть ли в классе C конструктор по умолчанию, который ничего не делает? В противном случае напишите еще один конструктор, который принимает NULL в качестве аргумента и делает его явным для безопасности и ничего не делает.   -  person DumbCoder    schedule 20.10.2011
comment
До сих пор это не так. И хотя я не хотел, чтобы у него был конструктор по умолчанию, похоже, мне просто нужно изменить его, чтобы он был.   -  person Atmocreations    schedule 20.10.2011
comment
К сожалению, принятый ответ неверен. Не забудьте прокрутить вниз...   -  person sdgfsdh    schedule 03.01.2017


Ответы (9)


То, что вы просите, запрещено - и это правильно. Это гарантирует, что каждый элемент будет правильно инициализирован. Не пытайтесь обойти это — постарайтесь структурировать свои классы, чтобы они с этим работали.

Идея:

  • C имеет конструктор, который ничего не делает
  • C имеет метод инициализации, который делает класс пригодным для использования
  • C отслеживает, правильно ли он был инициализирован, и возвращает соответствующие ошибки, если используется без инициализации.
person Tobias Langner    schedule 20.10.2011
comment
Ну... частично не согласен. В конструкторе я также гарантирую, что каждый член правильно инициализирован. Если у вас есть указатель, он тоже неправильно инициализирован. Но, похоже, я должен пойти другим путем. Благодарю вас! - person Atmocreations; 20.10.2011
comment
Хорошо. Я думаю, что единственным решением является создание пустого конструктора по умолчанию. Остальные решения кажутся хорошими, но не подходят для моей текущей проблемы. Спасибо! - person Atmocreations; 20.10.2011
comment
@Atmocreations: сам указатель инициализирован правильно - в соответствии с конструктором указателей по умолчанию. Обычно это не имеет смысла - если вам повезет, это 0. - person Tobias Langner; 20.10.2011


Вы не можете.

Все переменные-члены полностью создаются при вводе блока кода конструктора. Это означает, что должны быть вызваны конструкторы.

Но вы можете обойти это ограничение.

// Header-File
class A
{
    struct Initer
    {
         Initer(B& b)
             : m_b(b)
         {
             m_b.doSomething();
             m_b.doMore();
         }
         operator int()  // assuming getSomeValue() returns int.
         {
             return m_b.getSomeValue();
         }
         B& m_b;
    };
    public:
    A();

    private:   // order important.
    B m_B;
    C m_C;
};


// cpp-File
A::A()
: m_B(1)
, m_C(Initer(m_B))
{
}
person Martin York    schedule 20.10.2011
comment
Не могли бы вы сделать что-то подобное со статическим методом Init? Например. m_B(1), m_C(Init(m_B)). Я хотел указать это в своем ответе, но не был уверен, законно это или нет. - person Merlyn Morgan-Graham; 20.10.2011
comment
@merlyn-morgan-graham: Да. Вам нужно определить оператор преобразования, который будет преобразовывать объект типа Init в тип T (где T — это тот же тип результата, что и для getSomeValue()). - person Martin York; 20.10.2011
comment
Я хотел определить Init как: private: static int Init(B& b) { /* init here */ return b.getSomeValue(); }. Кажется, ваш комментарий к другому ответу может подтвердить, что это работает. - person Merlyn Morgan-Graham; 20.10.2011
comment
@Merlyn Morgan-Graham: Да, использование частного статического метода чище, чем это. Я бы согласился с идеей Влада теперь, когда я ее увидел. - person Martin York; 20.10.2011
comment
@LokiAstari: Спасибо! Мне нравится это решение. Хотя мне проще просто создать пустой конструктор для C. - person Atmocreations; 20.10.2011

Я не вижу хорошего способа добиться того, чего вы хотите. Это должно быть обходным путем:

// Header-File
class A
{
    public:
    A();

    private:
    B m_B;
    C m_C;
    static int prepareC(B& b);
};

// cpp-File
A::A()
: m_B(1)
, m_C(prepareC(m_B))
{
}

int A::prepareC(B& b)
{
    b.doSomething();
    b.doMore();
    return b.getSomeValue();
}

Убедитесь, что m_B.doSomething(), m_B.doMore() и m_B.getSomeValue() не касаются m_C (прямо или косвенно).


Как правильно отмечает @Tobias, это решение зависит от порядка инициализации. Вы должны убедиться, что определения m_B и m_C находятся в этом порядке.


Обновил код по идее @Loki.

person Vlad    schedule 20.10.2011
comment
вы должны знать, что поведение this зависит от правильного порядка определений m_B и m_C. Он работает до тех пор, пока m_C определен после m_B. Пожалуйста, прокомментируйте как таковой. В остальном мне нравится эта версия. - person Tobias Langner; 20.10.2011
comment
+1: хотя я бы сделал prepareC() статическим членом и передал b в качестве параметра. - person Martin York; 20.10.2011
comment
Похоже на возможность для меня. Почему я должен следить за тем, чтобы методы m_B не затрагивали m_C? - person Atmocreations; 20.10.2011
comment
@Atmocreations: Потому что это было бы неопределенным поведением: m_C все еще не инициализирован! - person Vlad; 20.10.2011
comment
@Atmocreations: просто не передавайте m_C в качестве параметра конструктору m_B. И убедитесь, что m_C объявлен после m_B в объявлении класса. - person Martin York; 20.10.2011
comment
@Loki: существует небольшая вероятность того, что методы m_B получают доступ к создаваемому экземпляру A (например: A передает this конструктору m_B). Поэтому лучше перепроверить. :) Но для обычных случаев достаточно вашего совета. - person Vlad; 20.10.2011

Указатель звучит как единственное чистое решение для меня. Единственное другое решение, которое я вижу, - это иметь конструктор по умолчанию для C, который ничего не делает, и иметь метод инициализации в C, который вы позже вызовете сами.

m_C.Initialise( m_B.getSomeValue());

person Martin    schedule 20.10.2011

Проще всего хранить указатели на B и C. Они могут быть инициализированы до 0, опуская любую конструкцию. Будьте осторожны, чтобы не разыменовать нулевой указатель и не удалить его в деструкторе A (или использовать std::unique_ptr/boost::scoped_ptr).

Но почему бы не инициализировать сначала m_B (с помощью надлежащего вызова конструктора, а не в A::A(), а затем использовать этот инициализированный экземпляр B для инициализации m_C? Это потребует небольшой перезаписи, но я уверен, что это будет стоить очистки кода.

person rubenvb    schedule 20.10.2011

Если вы не хотите выделять его динамически, используя new из соображений безопасности кода/исключений, вы можете использовать std::unique_ptr или std::auto_ptr для решения этой проблемы.

Решение, позволяющее избежать new, состоит в том, чтобы отредактировать C, чтобы иметь двухэтапный процесс инициализации. Затем конструктор создаст объект «зомби», и вам нужно будет вызвать метод Initialize для этого экземпляра m_C, чтобы завершить инициализацию. Это похоже на существующие случаи, когда вы могли передать NULL конструктору, а затем вернуться к инициализации объекта.

Изменить:

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

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

Просто используйте статический метод в качестве посредника при инициализации:

class A
{
public:
    A();

private:
    static int InitFromB(B& b)
    {
        b.doSomething();
        b.doMore();
        return b.getSomeValue();
    }

    // m_B must be initialized before m_C
    B m_B;
    C m_C;
};

A::A()
    : m_B(1)
    , m_C(InitFromB(m_B))
{
}

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

person Merlyn Morgan-Graham    schedule 20.10.2011

Просто используйте запятые:

A::A()
  : m_B(1)
  , m_c(m_B.doSomething(), m_B.doMore(), m_B.getSomeValue())
{
}

Очевидно, как объяснили другие, m_B лучше объявить до m_C, иначе m_B.doSomething() вызовет неопределенное поведение.

person MSalters    schedule 20.10.2011
comment
Предполагая, что C не имеет конструктора с 3 параметрами. - person Martin York; 20.10.2011
comment
В этом случае вы добавляете скобки: :m_c((foo(), bar(), baz())) - person MSalters; 20.10.2011
comment
спасибо за ответ. идея хороша и хорошо работает для нескольких инструкций. но, учитывая количество инструкций, которые мне нужно выполнить, прежде чем я смогу создать экземпляр m_C, thifs выглядит просто уродливым для меня. - person Atmocreations; 20.10.2011

Здесь у нас есть строительные блоки:

#include <iostream>

class C
{
public:
  C(int i){std::cout << "C::C(" << i << ")" << std::endl;}
};

class B
{
public:
  B(int i){std::cout << "B::B(" << i << ")" << std::endl;}
  void doSomething(){std::cout << "B::doSomething()" << std::endl;}
  void doMore(){std::cout << "B::doMore()" << std::endl;}
  int getSomeValue(){return 42;}
};

Если вы хотите создать новый тип конструкции для B, рассмотрите возможность создания производного класса:

class B1 : public B
{
public:
  B1() : B(1)
  {
    doSomething();
    doMore();
  }
};

Теперь используйте класс B1, производный от B:

class A
{
private:
  B1 _b;
  C _c;
public:
  A() : _c(_b.getSomeValue()){std::cout << "A::A()" << std::endl;}
};

А потом:

int main()
{
  A a;
}

Выход:

B::B(1)
B::doSomething()
B::doMore()
C::C(42)
A::A()
person Jossi    schedule 24.06.2014