Вопрос по c++ – Правильный способ условно инициализировать переменную-член C ++?

10

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

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, но я надеюсь, что есть более простой способ.

Edit: Я должен был сказать ранее, но у моей настоящей проблемы есть дополнительное осложнение: мне нужно вызвать метод перед инициализацией 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);
    }
};

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

Ваш Ответ

6   ответов
5
 MyClass(int xyz) : m_class(xyz==42 ? 12 : 32) {}

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

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.
    }
};
0

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);
    }
};

Если вы все-таки хотите сохранить тот же синтаксис. Инициализация члена более эффективна.

0

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

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 в деструкторе.

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

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*/)
    { }
};

Наверное, чище с завода:

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

//...
 MyClass(int xyz) : m_class(create_member(xyz))
Да, печатать слишком быстро для моего же блага. Лично я все же перенесу логику в MemberClass или фабрику.
Вы создаете безымянный объект, а затем используете copy-ctor для инициализации члена, а не для его инициализации напрямую.
0

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

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

Иногда есть причины сделать конструктор по умолчанию закрытым, но он всегда должен быть там ...
Вы должны иметь параметр по умолчанию, который переводит его в некое известное состояние зомби. Не идеально, но иногда единственный способ.
Иногда есть причины не иметь конструктор по умолчанию.
25

используйте функцию

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 до завершения списка инициализатора конструктора. Функция должна быть членом своего базового класса и базового класса & apos; 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-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 объект не может быть создан. Надеюсь это поможет :)

это также усложняется, если конструктор вашей переменной принимает несколько аргументов; тогда вам понадобится одна из этих функций для каждого аргумента
Спасибо! Я обновил свой вопрос - мне нужно запустить метод перед созданием m_class. Это возможно? Возможно яcould сделайте это в «classInit», но это не будет очень элегантно. Pedro d'Aquino
Когда я увидел использование оператора запятой, я понял, что человек, отвечающий на этот вопрос, был умен. Когда я увидел использование функционального блока try, я понял, что человек, отвечающий на этот вопрос, горит. Когда я увидел, что человек, отвечающий на этот вопрос, был слегка ... ну, я думаю, я уже знал, что это был он :)
+1 за краткую рекомендацию умных вещей, а не динамическую / основанную на указателе инициализацию, как другие, неполноценные ответы. C ++, имеющий такие вещи, как RVO и троичный оператор, означает, что нам не нужно пукать сnew/ указатели в этом случае. Я думал, что наша мантра не должна использовать динамическое распределение, если у нас нет веских причин? Это не одно.
Узнал много, спасибо! :-) Pedro d'Aquino

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