Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Для новичков > Инициализатор элементов


Автор: hoz 27.4.2014, 12:48
Часто встречаю такие приёмы, когда переменные инициализируются посредством инициализатора элементов. Хочется понять это просто альтернатива, которая не является необходимой вообще или всё-таки бывает необходима? Например:
Код

class Test
{
public:
   Text (int = 0);
   void print() const;
private:
   int x;
}

Test::Test (int value)
   : x (value)
{
//пустое тело
}
//дальше..
//объявляем элементы..
//..класса

переменной x же можно было присвоить значение value внутри конструктора, зачем было объявлять его через : после заголовка?

Автор: BlackSpace 27.4.2014, 18:05
hoz, да это альтернативный способ, но он боле эффективен для некоторых типов данных, ну а в некоторых случаях без него не обойтись.
В C++ - при создании объекта все члены данных объекта должны быть созданы до вызова конструктора. 
Список инициализации позволяет задать начальные данные объекту в момент его создания.

В процессе создания членов данных, которые сами являются объектами, должны быть вызваны конструкторы. 
В теле же конструктора, когда происходит присвоение значения объекту - объект не создается, а лишь модифицируется.
Думаю тут понятно, что проще сразу присвоить нужные значения членам объекта, чем сначала присвоить то, что есть в конструкторе объекта, а потом менять.

Плюс еще для const члена данных не прокатит присваивание. Только инициализация при создании. Также ссылочный тип данных.
Вот пример с const.

Код

#include <iostream>

using namespace std;

class A {
public:
    const int a;

    A() : a( 6 ) {
    }
    
    /*
     A() {
     a = 6; // не прокатит
     }
     */
};

int main() {
    A obj;
    cout << obj.a << endl;
    return 0;
}

Автор: hoz 27.4.2014, 22:11
BlackSpace, я так понимаю данный способ позволяет обойти ограничение того, что константные элементы нельзя изменять в пределах класса. А так ещё до входа в тело конструктора можно инициализировать элемент-данные. Так? А что по поводу большей эффективность для некоторых типов?

Цитата(BlackSpace @  27.4.2014,  18:05 Найти цитируемый пост)
В C++ - при создании объекта все члены данных объекта должны быть созданы до вызова конструктора. Список инициализации позволяет задать начальные данные объекту в момент его создания.

А вот тут не совсем понятно. Тут имеется ввиду данные других классов? Т.е. данные, которые не объявлены в теле класса?

Автор: BlackSpace 27.4.2014, 23:35
hoz, попробую пояснить на примерах.

Пример 1.
Код

#include <iostream>

using namespace std;

class A {
public:
    string str;

    A() : str( "123" ){

    }

};

int main() {

    A obj;
    cout << obj.str << endl;

    return 0;
}


Пример 2
Код

#include <iostream>

using namespace std;

class A {
public:
    string str;

    A() {
        str = "123";
    }

};

int main() {

    A obj;
    cout << obj.str << endl;

    return 0;
}



1) Все члены данных объекта должны быть созданы до вызова конструктора.
2) Если у объекта класса N есть член данных, который является тоже объектом некого класса M, то до вызова конструктора класса N создается объект класса M. 
 - Но как? С помощью своего конструктора. 
 - А какие значения членам данных объекта класса M присваиваются?  Такие, которые заложены в его соответствующем конструкторе. 
3) Так вот список инициализации позволяет в момент создания объекта класса N - инициализировать его члены данных. Т.е. инициализировать в данном случае член класса M.

По примерам выше поясню разницу.
str является объектом класса string.
В примере 1 член данных str инициализируется строкой 123 сразу в момент создания объекта класса A.
В примере 2 член данных str инициализируется сначала своим конструктором по умолчанию, только потом происходит модификация строки, когда в теле конструктора присваивание происходит.

Чувствуете отличие? В первом примере мы не вызывали конструктор по умолчанию для члена данных str, а использовали конструктор класса string с одним аргументом.
Вот тут про эффективность думаю понять можно.

Цитата

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


Ничего подобного. Ничего в данном случае не обходится. В классе есть константный член и он не меняется. Его надо как-то инициализировать же при создании объекта класса. Вот в списке инициализации и делается это.

Автор: borisbn 28.4.2014, 09:31
Позволю себе дополнить пример BlackSpace
Код
struct Tracer {
   Tracer(void)
      :m_name("(none)")
   {
      std::cout << "[" << m_name << "]    " << __PRETTY_FUNCTION__ << std::endl;
   }
   Tracer(const std::string & name)
      :m_name(name)
   {
      std::cout << "[" << m_name << "]    " << __PRETTY_FUNCTION__ << std::endl;
   }
   Tracer(const Tracer & other)
      :m_name("from " + other.m_name)
   {
      std::cout << "[" << m_name << "]    " << __PRETTY_FUNCTION__ << std::endl;
   }
   Tracer & operator=(const Tracer & other) {
      m_name = "from " + other.m_name;
      std::cout << "[" << m_name << "]    " << __PRETTY_FUNCTION__ << std::endl;
      return *this;
   }
   ~Tracer() {
      std::cout << "[" << m_name << "]    " << __PRETTY_FUNCTION__ << std::endl;
   }
 
   std::string m_name;
};
 
struct A {
    A() : a( "init. list" ) {}
    Tracer a;
};
 
struct B {
    B() {
        b = Tracer( "c-tor" );
    }
    Tracer b;
};
 
int main() {
    {
        A x;
    }
    cout << "1" << endl;
    {
        B x;
    }
    cout << "2" << endl;
}


Вывод:
Цитата
[init. list]    Tracer::Tracer(const string&)
[init. list]    Tracer::~Tracer()
1
[(none)]    Tracer::Tracer()
[c-tor]    Tracer::Tracer(const string&)
[from c-tor]    Tracer& Tracer::operator=(const Tracer&)
[c-tor]    Tracer::~Tracer()
[from c-tor]    Tracer::~Tracer()
2


Вот - http://ideone.com/X4AvnZ - можешь "поиграться"

Автор: hoz 1.5.2014, 19:11
Цитата(BlackSpace @  27.4.2014,  23:35 Найти цитируемый пост)
2) Если у объекта класса N есть член данных, который является тоже объектом некого класса M, то до вызова конструктора класса N создается объект класса M. 
 - Но как? С помощью своего конструктора

Согласен. Изначально отрабатывают конструкторы объектов, которые являются членами данного класса (В нашем случает объекта класса N). И, отрабатывают они, как я понимаю, в любом случае.

Цитата(BlackSpace @  27.4.2014,  23:35 Найти цитируемый пост)
 - А какие значения членам данных объекта класса M присваиваются?  Такие, которые заложены в его соответствующем конструкторе. 

Это понятно.

Цитата(BlackSpace @  27.4.2014,  23:35 Найти цитируемый пост)
В примере 1 член данных str инициализируется строкой 123 сразу в момент создания объекта класса A.

А как же собственный конструктор класса string ? Судя по ответу, он типа не участвует в инициализации данных класса... А вместо него инициализация происходит напрямую в момент создания объекта класса А.

Цитата(BlackSpace @  27.4.2014,  23:35 Найти цитируемый пост)
В примере 2 член данных str инициализируется сначала своим конструктором по умолчанию, только потом происходит модификация строки, когда в теле конструктора присваивание происходит.

А вот тут я недопонимаю. Сразу не писал, пытался вникунуть, но.. Почему-то тут член данных str инициализируется изначально своим конструктором по умолчанию, а потом уже в теле конструктора.
Удивляет меня то, что согласно теории, в любом учебнике сказано, что при создании объекта конструктор вызывается автоматически. Поэтому странно слышать, что в первом случае минуя автоматический конструктор по умолчанию член данных инициализируется сразу заданным значение. Это как понимать?

Цитата(BlackSpace @  27.4.2014,  23:35 Найти цитируемый пост)
Чувствуете отличие? В первом примере мы не вызывали конструктор по умолчанию для члена данных str, а использовали конструктор класса string с одним аргументом.

Как выше я написал, не стыковка выходит. Если согласно документации конструктор вызывается автоматически, и, всегда, то.. как его можно не вызывать? smile 

Автор: feodorv 1.5.2014, 21:09
Цитата(hoz @  1.5.2014,  20:11 Найти цитируемый пост)
А как же собственный конструктор класса string ? 

hoz, конструкторов у одного и того же класса может быть несколько. Как с различающимися параметрами, так и без параметров (или с параметрами по умолчанию), последний случай и есть конструктор по умолчанию. Если пользователь явно указал вызов конструктора для какого-либо члена класса, то для него конструктор по умолчанию не вызывается (да и зачем, если и так был вызван другой конструктор с параметрами).
Цитата(BlackSpace @  28.4.2014,  00:35 Найти цитируемый пост)
class A {
public:
    string str;
    A() 
//  вот здесь для str вызывается конструктор по умолчанию
    {
        str = "123";
    }
};


Цитата(BlackSpace @  28.4.2014,  00:35 Найти цитируемый пост)
class A {
public:
    string str;
    A() : str( "123" )
//  а вот здесь для str не вызывается конструктор по умолчанию (str уже инициализирована)
    {
    }
};


Автор: hoz 1.5.2014, 23:45
Цитата(feodorv @  1.5.2014,  21:09 Найти цитируемый пост)
hoz, конструкторов у одного и того же класса может быть несколько. Как с различающимися параметрами, так и без параметров (или с параметрами по умолчанию), последний случай и есть конструктор по умолчанию. 

Но вызывается то один конструктор как ни крути. Хотя их может быть несколько..

Цитата(feodorv @  1.5.2014,  21:09 Найти цитируемый пост)
Если пользователь явно указал вызов конструктора для какого-либо члена класса, то для него конструктор по умолчанию не вызывается (да и зачем, если и так был вызван другой конструктор с параметрами).

А для остальных вызывается? Имею ввиду если членов класса, скажем так, - 10. Явно инициалируется 1 член. Тогда 9 оставшихся членов-данных инициализируются конструктором по умолчанию, верно?

Автор: BlackSpace 2.5.2014, 04:44
hoz,  smile 

В моем примере 1 для члена данных str вызывается один конструктор ( с одним параметром в данном случае ). И все. str сразу имеет нужный нам вид - "123".
В примере 2 для того же str вызывается сначала конструктор по умолчанию, а лишь потом идет работа по модификации str.

Во многих случаях гораздо эффективнее список инициализации, так как он позволяет избежать вызова конструктора по умолчанию ( после которого мы должны модифицировать поле ( str )в теле конструктора ( класса A ) ), а вызвать сразу нужный нам конструктор. А константные или ссылочные поля только в этом списке и можно инициализировать.
Мой пример не демонстрирует непосредственно эффективность, но общий принцип должен быть понятен.

Цитата

А для остальных вызывается? Имею ввиду если членов класса, скажем так, - 10. Явно инициалируется 1 член. Тогда 9 оставшихся членов-данных инициализируются конструктором по умолчанию, верно?


Так, приведу еще пример. Правда, 10 классов для примера я не стал писать. Думаю, что для примера сойдет 3 класса, которые будут типами полей в большем классе.
Три ( одинаковых для простоты примера ) класса A, B и C. Все имеют конструктор по умолчанию и конструктор с одним параметром. Используемый конструктор можно отследить на стандартном выводе. 
Два ( почти одинаковых ) класса CommonFirst и CommonSecond, которые содержат поля типов A, B и C. Разница между двумя классами - в конструкторе с тремя параметрами.

Код
#include <iostream>

using namespace std;

class A {
public:
    int value;

    // конструктор по умолчанию
    A() :
            value( 0 ) {
        cout << "Конструктор по умолчанию класса A" << endl;
    }

    // конструктор с одним параметром
    A( int val ) :
            value( val ) {
        cout << "Конструктор по с одним параметром класса A" << endl;
    }
};

class B {
public:
    int value;

    // конструктор по умолчанию
    B() :
            value( 0 ) {
        cout << "Конструктор по умолчанию класса B" << endl;
    }

    // конструктор с одним параметром
    B( int val ) :
            value( val ) {
        cout << "Конструктор по с одним параметром класса B" << endl;
    }
};

class C {
public:
    int value;

    // конструктор по умолчанию
    C() :
            value( 0 ) {
        cout << "Конструктор по умолчанию класса C" << endl;
    }

    // конструктор с одним параметром
    C( int val ) :
            value( val ) {
        cout << "Конструктор по с одним параметром класса C" << endl;
    }
};


// мы хотим, чтобы при создании объекта класса CommonFirst или класса CommonSecond
// с использованием конструктора с тремя параметрами - поля имели нужные нам значения.


class CommonFirst {
public:
    // поля класса имеют типы данных - другие классы
    A a;
    B b;
    C c;

    // конструктор с тремя параметрами
    // все поля, которым нужны определенные значения при создании объекта - добавляем в список инициализации
    CommonFirst( int _a, int _b, int _c ) :
            a( _a ), b( _b ), c( _c ) {
        // все три поля инициализированы нужными значениями при создании объекта класса CommonFirst
        // произошло это ДО фигурной скобки выше.
        cout << a.value << endl;
        cout << b.value << endl;
        cout << c.value << endl;
    }
};

class CommonSecond {
public:
    // поля класса имеют типы данных - другие классы
    A a;
    B b;
    C c;

    // конструктор с тремя параметрами
    // ТОЛЬКО одно поле из всех, которым нужны определенные значения при создании объекта - добавляем в список инициализации
    // остальные модифицируем ПОСЛЕ создания - уже в теле конструктора
    CommonSecond( int _a, int _b, int _c ) :
            a( _a ) {
        // для двух полей b и c будет вызван СНАЧАЛА конструктор по умолчанию.
        // произошло это ДО фигурной скобки выше.
        cout << a.value << endl; // инициализирован нужным значением при создании объекта класса CommonSecond
        cout << b.value << endl; // ноль
        cout << c.value << endl; // ноль
        // лишь потом будет модификация двух полей b и c.
        b.value = _b;
        c.value = _c;
    }
};

int main() {
    cout << "Создаем объект класса CommonFirst" << endl;
    CommonFirst first( 1, 2, 3 );

    cout << "Создаем объект класса CommonSecond" << endl;
    CommonSecond second( 1, 2, 3 );

    return 0;
}


Вывод программы
Код

Создаем объект класса CommonFirst
Конструктор по с одним параметром класса A
Конструктор по с одним параметром класса B
Конструктор по с одним параметром класса C
1
2
3
Создаем объект класса CommonSecond
Конструктор по с одним параметром класса A
Конструктор по умолчанию класса B
Конструктор по умолчанию класса C
1
0
0

Автор: vinter 2.5.2014, 17:22
Цитата(hoz @  2.5.2014,  00:45 Найти цитируемый пост)
Но вызывается то один конструктор как ни крути. Хотя их может быть несколько..

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

Цитата(hoz @  2.5.2014,  00:45 Найти цитируемый пост)
А для остальных вызывается? Имею ввиду если членов класса, скажем так, - 10. Явно инициалируется 1 член. Тогда 9 оставшихся членов-данных инициализируются конструктором по умолчанию, верно?

не у всех типов есть конструкторы. К примеру у базовых типов его нет. Для тех у которых есть конструктор по-умолчанию - да, вызывается.

Автор: xvr 5.5.2014, 16:59
Цитата(vinter @  2.5.2014,  17:22 Найти цитируемый пост)
в С++ есть делегирующие конструкторы

Точнее появились в С++11 (так к слову  smile )

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)