С++ Constructors vs Инициализация Список сравнения скорости - программирование
Подтвердить что ты не робот

С++ Constructors vs Инициализация Список сравнения скорости

Существуют ли различия в времени выполнения между конструкторами и списками инициализации? (или это просто вопрос кодирования). У меня есть набор объектов, которые нужно создавать часто и хотели бы знать, есть ли какое-либо увеличение производительности, используя списки инициализации вместо конструкторов.

Если бы я должен был создать миллион экземпляров класса A и еще один миллион класса B, выбор был бы лучше (объекты представляют пакеты, сгенерированные в сети, следовательно, эти числа).

 class A {
   private:
     int a, b;

   public:
     A(int a_var, int b_var):a(a_var), b(b_var) {}; 
 };

 class B {
   private:
     int a, b;

   public:
     B(int a_var, int b_var) {
        a = a_var;
        b = b_var;
     }
};

Если какой-либо из конструкторов быстрее, чем другой для примитивных типов (как в примере), будет ли он быстрее, если a и b должны быть заменены типами?

Пример типа:

 class AType {
   private:
     string a, b;

   public:
     AType(string a_var, string b_var):a(a_var), b(b_var) {}; 
};
4b9b3361

Ответ 1

Разница заключается в типах без тривиального конструктора по умолчанию, который вызывается компилятором в вашем классе B. Ваш класс B эквивалентен:

 class B {
   private:
     SleepyInt a, b;

   public:
     // takes at least 20s
     B(int a_var, int b_var) : a(), b()
     //                      ^^^^^^^^^^ 
     {
        a = a_var;
        b = b_var;
     }
  };

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

Какой-то забавный пример, просто чтобы проиллюстрировать разницу:

class SleepyInt {
public:
  SleepyInt () { 
    std::this_thread::sleep_for(std::chrono::milliseconds( 10000 ));  
  }
  SleepyInt (int i) {}
  SleepyInt & operator = (int i) { return *this; }
};

class A {
   private:
     SleepyInt a, b;

   public:
     A(int a_var, int b_var):a(a_var), b(b_var) {}; 
 };

 class B {
   private:
     SleepyInt a, b;

   public:
     // takes at least 20s
     B(int a_var, int b_var) {
        a = a_var;
        b = b_var;
     }
};

Ответ 2

Общепринятой практикой является использование списков инициализации, а не назначение в конструкторе, и для этого есть веская причина.

Списки инициализации могут использоваться для инициализации как POD (Обычные старые данные), так и пользовательские типы. При инициализации типа POD эффект точно такой же, как и оператор присваивания, что означает отсутствие разницы в производительности между списками инициализации или присваиванием в конструкторе для типов POD.

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

Итак, чтобы ответить на вопрос, существует разница в производительности, но только при инициализации не-POD-типов.

Ответ 3

Если члены имеют более или менее сложные типы, то инициализация присваивания сначала вызовет вызов конструктора по умолчанию, а затем operator=, что может занять больше времени.

Ответ 4

Не будет улучшения производительности, если типы являются встроенными/внутренними.

Это сказало:

Заключение: при прочих равных условиях ваш код будет работать быстрее, если вы используете списки инициализации, а не назначение.

Ответ 5

Список инициализации - это полезные ссылочные типы, объекты класса членов или константные члены. В противном случае требуется больше времени.

Посмотрите на мой тестовый код:

#include <iostream>
#include <ctime>

using namespace std;

class A{
    int a;
public:
    A(int a_):a(a_){}
};

class B{
    int b;
public:
    B(){
    }

    B(int b_){
        b=b_;
    }
};

class C{
    B b;
public:
    C(int c_):b(c_){
    }
};

class D{
    B b;
public:
    D(int d_){
        b=d_;
    }
};

int main()
{
    clock_t start1[10], start2[10], end1[10], end2[10];
    for(int j=0;j<10;j++){
        start1[j]=clock();
        for(int i=0;i<100000;i++){   
            A *newA=new A(i);
            delete newA;
        }
        end1[j]=clock();
        start2[j]=clock();
        for(int i=0;i<100000;i++){   
            B *newB=new B(i);
            delete newB;
        }
        end2[j]=clock();
    }
    double avg1=0, avg2=0;
    for(int i=0;i<10;i++){
        avg1+=(end1[i]-start1[i]);
        avg2+=(end2[i]-start2[i]);
    }
    cout << avg1/avg2 << endl;

    for(int j=0;j<10;j++){
        start1[j]=clock();
        for(int i=0;i<100000;i++){   
            C *newC=new C(i);
            delete newC;
        }
        end1[j]=clock();
        start2[j]=clock();
        for(int i=0;i<100000;i++){   
            D *newD=new D(i);
            delete newD;
        }
        end2[j]=clock();
    }
    avg1=avg2=0;
    for(int i=0;i<10;i++){
        avg1+=(end1[i]-start1[i]);
        avg2+=(end2[i]-start2[i]);
    }
    cout << avg1/avg2 << endl;

    system("pause");
    return 0;
}



Пример таких выходов:

1.02391
0.934741