2014-12-04 3 views
7

В профилировании моей программы я понял, что 10% кода потрачено на глупый конструктор std::complex<double>(), используя new std::complex<double>[size_of_array].Неинициализированный std :: complex constructor при использовании 'new'

Я искал в Интернете, и конструктор по умолчанию для std::complex, кажется, воспринимает как реальную и мнимую части значения double(). Поскольку C++ не инициализирует двойные числа, мне интересно, почему g ++ пытается инициализировать std::complex с нулями, и смогу ли я обойти это через всю программу в некотором роде (*)

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

Редактировать: как указано ниже, это был надзор на моей стороне. Конструктор по умолчанию имеет пустые конструктор для реальной и мнимой части (http://en.cppreference.com/w/cpp/numeric/complex/complex)

complex(const T& re = T(), const T& im = T()); 

но спецификация затем вводит специальные случаи для двойной

complex(double re = 0.0, double im = 0.0); 

Именно этот особый случай, который представляет все накладные расходы, так как он обходит фактический конструктор по умолчанию «double», который ничего не делает (так же, как для int, long, float и т. д.).

+1

Прочитано, например. [эта ссылка на конструктор std :: complex] (http://en.cppreference.com/w/cpp/numeric/complex/complex). Значения указываются так, чтобы всегда инициализироваться с использованием аргументов по умолчанию. –

+1

@ Juanjo, следующий за советом Гоза, является гораздо более простой/лучшей альтернативой, но если вам абсолютно необходимо это делать с массивами, то вам, возможно, придется сделать это с помощью _placement 'new'_; не очень мудрый вариант. – legends2k

+0

@JoachimPileborg thx для указателя, я понял, что только прочитал первый комплекс линий (const T & re = T(), const T & im = T()); и предположил, что double() поэтому неинициализирован. Я чувствую, что особые случаи ниже являются непоследовательными, но им придется жить с этим. Жаль, потому что эти накладные расходы пронизывают все мои библиотеки, включая определения переменных и т. П. Вам придется работать через них один за другим: -/ – Juanjo

ответ

5

Интересно, почему г ++ надоедает инициализировать станд :: комплекс с нулями

Поскольку стандарт говорит, что он должен сделать так, конструктор по умолчанию объявляется как:

constexpr complex(double re = 0.0, double im = 0.0); 

так это устанавливает оба члена в ноль.

Это нормально для стандартной библиотеки для безопасной инициализации типов, а не оставлять их неинициализированными, как вы получите с встроенными типами, такими как double и int*, например std::vector<double> нуля инициализирует его элементы тоже, если вы измените его размер так, чтобы добавляются новые элементы. Вы можете контролировать это для vector, не добавляя элементы в vector, пока не узнаете, какие значения вы хотите иметь.

Одним из возможных Обойти complex является использование типа, который не делает инициализацию:

struct D 
{ 
    D() noexcept { }; // does not initialize val! 
    D(double d) noexcept : val(d) { } 
    operator double() const noexcept { return val; } 
    D& operator=(double d) noexcept { val = d; return *this; } 
    double val; 
}; 

Теперь, если вы используете std::complex<D> конструктор по умолчанию ничего не делает. Добавьте explicit к конвертирующему конструктору и/или оператору преобразования в соответствии с вашим вкусом.

+0

Хорошо, но это довольно абсурдно, потому что «double» и «int» встроены, а std :: complex - не что иное, как расширение их. Нельзя пренебрегать ударом по производительности, особенно в приложениях с интенсивным вычислением, которые, я уверяю вас, всегда заботятся об инициализации своих переменных, если это необходимо. – Juanjo

+0

Нет, это не расширение. Быть частью грамматики языка (встроенные) отличается от типов, созданных с использованием языка (определяемых пользователем типов). С точки зрения языка это просто «класс», без каких-либо предположений невозможно сделать это. Его поведение зависит от дизайнера этого типа. – legends2k

+0

@ Juanjo, я заверяю вас, что все утверждают, что всегда инициализируют переменные, если им это нужно, но все-таки ошибки все еще происходят ;-) В этом случае стандарт выбрал безопасность по производительности. Это будет правильным решением для некоторых приложений и неправильным для некоторых других. Я обновляю свой ответ обходным путем. –

4

Существует простой способ сделать это. Если вы «зарезервируете» память с помощью std :: vector, это будет намного быстрее, потому что он не вызывает конструктор для каждого элемента.

т.е. это:

std::vector< std::complex<double> > vec; 
vec.reserve(256); 
for(int i = 0; i < 256; i++) 
{ 
    vec.push_back(std::complex<double>(1, 1)); 
} 

будет значительно быстрее, чем это:

std::complex<double>* arr = new std::complex<double>[256]; 
for(int i = 0; i < 256; i++) 
{ 
    arr[i](std::complex<double>(1, 1)); 
} 
delete[] arr; 

, потому что конструктор вызывается только один раз в первом примере.

У этого есть дополнительное преимущество, что у вас есть RAII на вашей стороне, и «vec» будет автоматически выпущен, когда он выходит из сферы действия.

+3

Примечание: 'vec.emplace_back (1,1);' будет работать. – WhozCraig

+1

Я строю свои собственные числовые структуры (https://github.com/juanjosegarciaripoll/tensor), и я бы предпочел не строить их поверх std :: vector, у которого есть свои накладные расходы, которые становятся значительными при выполнении большого количества матрицы умножения и т.п. Мне хотелось бы воспроизвести «поведение вектора w.r.t. неинициализированная память, теперь, когда я вижу, что комитет C++ совершил тупую ошибку, превысив std :: complex. – Juanjo

+0

@Juanjo: malloc и free не будут называть конструктор, но это не действительно C++-решение! – Goz

0

следующее из кода, который я разрабатываю.

Добавлено позже: Как M.M. отмечается в комментариях, поведение технически не определено. Теперь я вижу, что если какая-то неряшливая ласка должна была изменить реализацию std :: complex, так что она не могла бы быть тривиально построена/разрушена, это принесло бы обрезку. См. Также пример на http://en.cppreference.com/w/cpp/types/aligned_storage.

#include <complex> 
#include <type_traits> 

typedef std::complex<double> complex; 

// Static array of complex that does not initialize 
// with zeros (or anything). 

template<unsigned N> 
struct carray { 
    typedef 
     std::aligned_storage<sizeof(complex), alignof(complex)>::type raw; 

    raw val[N]; 

    complex& operator[] (unsigned idx) { 
     return reinterpret_cast<complex&> (val[idx]); 
    } 

    complex* begin() { return &operator[](0); } 
    complex* end() { return &operator[](N); } 
}; 
+0

Это неопределенное поведение: вы получаете доступ к хранилищу без каких-либо объектов в нем, как если бы у него были объекты. См. Http://stackoverflow.com/questions/40873520/. Даже случай POD в этом потоке здесь не применяется, поскольку 'std :: complex' имеет нетривиальную инициализацию. –

+0

@ M.M. Я не представляю, как это закончится. Я не собираюсь копать эту чащу адвокатов. Если вы скажете, что это неопределенное, я верю. Итак, как это сделать? Является ли std :: complex бесполезным для языка педант, если все эти безвозвратные нули являются разрывами? Что делать с матерью? Отправьте ответ, пожалуйста. –

+0

@ M.M Я должен был спросить: std :: complex бесполезен, если нули нельзя терпеть.? Эта специализация для типа double - королевская PITA. –

Смежные вопросы