Правильный способ условно инициализировать переменную-член C ++? - PullRequest
10 голосов
/ 18 июня 2009

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

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

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

Ответы [ 6 ]

25 голосов
/ 18 июня 2009

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

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-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. Надеюсь, это поможет:)

5 голосов
/ 18 июня 2009
 MyClass(int xyz) : m_class(xyz==42 ? 12 : 32) {}

Чтобы ответить на ваш исправленный вопрос, это немного сложно. Самый простой способ - сделать указатель m_class. Если вы действительно хотите использовать его в качестве члена данных, вам нужно проявить творческий подход. Создайте новый класс (лучше всего, если он определен внутри MyClass). Пусть ctor будет функцией, которую нужно вызвать. Включите его 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.
    }
};
5 голосов
/ 18 июня 2009

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

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))
0 голосов
/ 18 июня 2009

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

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

0 голосов
/ 18 июня 2009

Попробуйте это:

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

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

0 голосов
/ 18 июня 2009

Или:

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

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

...