Еще один запрос инициализации объекта C ++ - PullRequest
1 голос
/ 28 марта 2012

У меня есть этот класс, который имеет много членов класса и много разных конструкторов.

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

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

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

Быстрый пример:

class A
{
public:
  A();
  A( B b );
  A( int i );
  // A( .... ); plenty of them


private:
  int member1, m2, m3,m4;
  bool b1,b2, b3;
  // ....
  // every time I add a member I have to modify the initialization lists

  // solution: agregate member initialization in a member function: 
  void init_members();    
}

// init list constructors
A::A() : m1(false), m2(false), m3(false), m4(true) .... // looong list
{
}

A::A( B b) : m1(b.state()), m2(false), m3(false), ... // loong list
{
}

// problem, if I use init_members:
void A::init_members()
{
  m1 = false;
  m2 = false;
  m3 = false;
// ...
}

A::A( int i ) : m1( true)
{
  init_members(); // overrides m1 !!!
}

Итак, мой вопрос: могу ли я смешать инициализатор списка и инициализаторы метода, чтобы инициализаторы списка имели приоритет над инициализатором метода?
В моем примере выше я хочу, чтобы m1 оставался true для последнего конструктора.

Примечание: я знаю, что мог бы переместить список инициализации после вызова метода, но это означает, что я бы присвоил членам значения дважды: один раз в init_members(), затем переопределивэто в конструкторе.Не достаточно оптимально: -)

Я надеялся на небольшую хитрость, если она у вас есть в наличии.

Ответы [ 3 ]

5 голосов
/ 28 марта 2012

В C ++ 11 одним из вариантов может быть делегирование конструктора, когда один конструктор просто вызывает один из других конструкторов, чтобы избежать дублирования кода.

Вот как это выглядит:

class A {
public:
    A(int,int,double);
    A(int,int);
    A(double);
    A();
private:
    ...
};

A::A(int a,int b,double c) {
    // the real work to initialize the class
}

// A(int,int) delegates to A(int,int,double), passing along a
// default value for the double
A::A(int a,int b) : A(a,b,0.0) {}

A::A(double c) : A(1,2,c) {} // A(double) delegates to A(int,int,double)

A::A() : A(1.0) {} // A() delegates to A(double)

Убедитесь, что вы не создаете никаких петель.Также обычно вы хотите, чтобы один конструктор выполнял большую часть реальной работы, в то время как другие просто собирали значения, которые они хотят передать этому конструктору.Мы назовем это «назначенным конструктором».Назначенный конструктор должен быть тем, который принимает большинство параметров и не использует никаких значений по умолчанию.В конце концов все конструкторы должны вызывать назначенный конструктор, прямо или косвенно.

Обратите внимание на шаблон: конструкторы, которые используют некоторые значения по умолчанию, передают эти значения по умолчанию конструкторам, которые используют меньше значений по умолчанию, пока вы не получите функцию без значений по умолчанию.совсем.Это противоположно тому, что вы пытаетесь сделать с помощью метода init_members().У вас есть функция, которая устанавливает все значения по умолчанию, а затем вы пытаетесь переопределить некоторые из них.Если вы не можете использовать функции C ++ 11, вам лучше эмулировать назначенный шаблон конструктора: init_members() будет вашим назначенным инициализатором и не будет иметь никаких значений по умолчанию.Вы можете использовать метод инициализатора для каждого конструктора, который принимает заданные аргументы и добавляет несколько значений по умолчанию, чтобы вызвать другую init_members перегрузку.

Однако одна проблема с назначенным инициализатором / конструктором заключается в том, что значения по умолчаниюразбросаны повсюду.Другой вариант в C ++ 11 помимо делегирования - «инициализация в классе», которая позволяет собирать все значения по умолчанию.

class A {
public:
    A(int,int,double);
    A(int,int);
    A(double);
    A();
private:
    int a = 1,b = 2; // in-class initialization gathers all the defaults together
    double c = 1.0;
};

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

A::A(int a,int b,double c) : a(a), b(b), c(c) {}
A::A(int a,int b) : a(a), b(b) {} // member c is automatically initialized to 1.0
A::A(double c) : c(c) {} // members a and be are automatically initialized to 1 and 2
A::A() {}; // all members are initialized with their in-class values.

Вот пример использования init_members():

class A {
public:
    A(int a,int b,double c) { init_members(a,b,c); }
    A(int a,int b) { init_members(a,b); }
    A(double c) {init_members(c);}
    A() { init_members(); }
private:
    void init_members(int,int,double) { ... }
    void init_members(int a,int b) { init_members(a,b,1.0); }
    void init_members(double c) { init_members(1,2,c); }
    void init_members() { init_members(1.0); }
    ...
};

Это значение метода инициализирует члены передМожно вызвать init_members(), поэтому члены инициализируются дважды.Я не уверен, что есть способ исправить это в C ++ 03.

1 голос
/ 28 марта 2012

В подобных случаях я не использую список инициализатора базы / члена (члены имеют значения «мусор» или «конструктор по умолчанию» в этой точке конструктора), и я использую функцию init_() (называемую от конструктора-тела). Затем конструкторы вызывают функцию init_(), и существует одна точка обслуживания.

Аналогично, моя функция clear() также будет вызывать функцию init_() для отдельной точки обслуживания со значениями по умолчанию.

Для вашего случая это будет:

A::A(void)
//...no Base/Member-Initializer list...
{
  init_members();
}

A::clear(void)
{
  init_members();
}

... и переопределение:

A::A(int override_m1)
{
  init_members();
  m1 = override_m1;
}
0 голосов
/ 28 марта 2012

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

class AKeyValueStorage {
    // would be some kind of shared storage if meant to be 
    // copyable and don't forget moving if your're on c++11!
    std::map<std::string, boost::any> mMembers;
public:
    template<class Key, class T>
    T const & Get(Key const & pKey) const
    {
        auto tTmp = mMembers.find(ToString(pKey));
        if (tTmp != mMembers.end()) {
            return boost::any_cast<T const &>(*tTmp); 
        }
        // throw if none, or return default
    }

    template<class Key, class T>
    void Set(Key const & pKey, T const & pValue) const
    {
        mMembers[ToString(pKey)] = pValue; // replace if found, insert if none
    }
};
...