Правильный способ условной инициализации переменной-члена С++?

Я уверен, что это очень простой вопрос. Следующий код показывает, что я пытаюсь сделать:

class MemberClass {
public:
    MemberClass(int abc){ }
};

class MyClass {
public:
    MemberClass m_class;
    MyClass(int xyz) {
        if(xyz == 42)
            m_class = MemberClass(12);
        else
            m_class = MemberClass(32);
    }
};

Это не скомпилируется, потому что m_class создается с помощью пустого конструктора (которого не существует). Каков правильный способ сделать это? Мое предположение заключается в использовании указателей и создании экземпляров m_class с помощью new, но я надеюсь, что есть более простой способ.

Изменить: Я должен был сказать ранее, но моя фактическая проблема имеет дополнительное усложнение: мне нужно вызвать метод до инициализации m_class, чтобы настроить среду. Итак:

class MyClass {
public:
    MemberClass m_class;
    MyClass(int xyz) {
        do_something(); // this must happen before m_class is created
        if(xyz == 42)
            m_class = MemberClass(12);
        else
            m_class = MemberClass(32);
    }
};

Можно ли добиться этого с помощью причудливых трюков списка инициализации?

Ответ 1

Используйте условный оператор. Если выражение больше, используйте функцию

class MyClass {
public:
    MemberClass m_class;
    MyClass(int xyz) : m_class(xyz == 42 ? 12 : 32) {

    }
};

class MyClass {
    static int classInit(int n) { ... }
public:
    MemberClass m_class;
    MyClass(int xyz) : m_class(classInit(xyz)) {

    }
};

Чтобы вызвать функцию до инициализации m_class, вы можете поместить структуру до этого элемента и использовать RAII

class MyClass {
    static int classInit(int n) { ... }
    struct EnvironmentInitializer {
        EnvironmentInitializer() {
            do_something();
        }
    } env_initializer;
public:
    MemberClass m_class;
    MyClass(int xyz) : m_class(classInit(xyz)) {

    }
};

Это вызовет do_something() перед инициализацией m_class. Обратите внимание, что вам не разрешено вызывать нестатические функции-члены MyClass до завершения списка инициализатора конструктора. Функция должна быть членом своего базового класса, и базовый класс "ctor" уже должен быть завершен для этого.

Также обратите внимание, что функция, конечно, всегда вызывается для каждого отдельного объекта, созданного не только для первого созданного объекта. Если вы хотите это сделать, вы можете создать статическую переменную в конструкторе инициализатора:

class MyClass {
    static int classInit(int n) { ... }
    struct EnvironmentInitializer {
        EnvironmentInitializer() {
            static int only_once = (do_something(), 0);
        }
    } env_initializer;
public:
    MemberClass m_class;
    MyClass(int xyz) : m_class(classInit(xyz)) {

    }
};

Он использует оператор запятой. Обратите внимание, что вы можете поймать любое исключение, созданное do_something, с помощью блока функции-try

class MyClass {
    static int classInit(int n) { ... }
    struct EnvironmentInitializer {
        EnvironmentInitializer() {
            static int only_once = (do_something(), 0);
        }
    } env_initializer;
public:
    MemberClass m_class;
    MyClass(int xyz) try : m_class(classInit(xyz)) {

    } catch(...) { /* handle exception */ }
};

Функция do_something будет вызываться снова в следующий раз, если она выбрала это исключение, из-за которого объект MyClass не удалось создать. Надеюсь, это поможет:)

Ответ 2

Используйте синтаксис списка инициализаторов:

class MyClass {
public:
    MemberClass m_class;
    MyClass(int xyz) : m_class(xyz == 42 ? MemberClass(12) : MemberClass(32)
                               /* see the comments, cleaner as xyz == 42 ? 12 : 32*/)
    { }
};

Вероятно, очиститель с factory:

MemberClass create_member(int x){
   if(xyz == 42)
     return MemberClass(12);
    // ...
}

//...
 MyClass(int xyz) : m_class(create_member(xyz))

Ответ 3

 MyClass(int xyz) : m_class(xyz==42 ? 12 : 32) {}

Чтобы ответить на ваш пересмотренный вопрос, это немного сложно. Самый простой способ - сделать указатель m_class указателем. Если вы действительно хотите, чтобы это было как член данных, вам нужно проявить творческий подход. Создайте новый класс (лучше всего, если он определен внутри MyClass). Пусть это будет функция, которую нужно вызвать. Включите его сначала среди объявлений членов данных (это сделает его первым instaniated).

class MyClass 
{
     class initer { public: initer() {
                    // this must happen before m_class is created
                    do_something();                        
                    }
                   }

    initer     dummy;
public:

    MemberClass m_class;
    MyClass(int xyz) : m_class(xyz==42? 12 : 43)
    {
        // dummy silently default ctor'ed before m_class.
    }
};

Ответ 4

Или:

class MemberClass {
public:
    MemberClass(int abc){ }
};

class MyClass {
public:
    MemberClass* m_class;
    MyClass(int xyz) {
        if(xyz == 42)
            m_class = new MemberClass(12);
        else
            m_class = new MemberClass(32);
    }
};

Если вы каким-то образом хотите сохранить один и тот же синтаксис. Членство в инициативе более эффективно.

Ответ 5

Попробуйте следующее:

class MemberClass
{
public:    
   MemberClass(int abc = 0){ }
};

Это дает значение по умолчанию и ваш конструктор по умолчанию.

Ответ 6

Чтобы инициализация произошла после того, как произойдет что-то другое, вам действительно нужно использовать указатели, что-то вроде этого:

class MyClass {
public:
    MemberClass * m_pClass;
    MyClass(int xyz) {
        do_something(); // this must happen before m_class is created
        if(xyz == 42)
            m_pClass = new MemberClass(12);
        else
            m_pClass = new MemberClass(32);
    }
};

Единственное различие заключается в том, что вам нужно получить доступ к переменным-членам как m_pClass->counter вместо m_class.counter и delete m_pClass в деструкторе.