【问题标题】:Uninitialized std::complex constructor when using 'new'使用“新”时未初始化的 std::complex 构造函数
【发布时间】:2015-02-02 03:21:54
【问题描述】:

在分析我的程序时,我意识到 10% 的代码用在了一个愚蠢的 std::complex<double>() 构造函数中,使用了 new std::complex<double>[size_of_array]

我在网上搜索过,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 等相同)。

【问题讨论】:

  • 阅读例如this std::complex constructor reference。这些值被指定为始终使用默认参数进行初始化。
  • @Juanjo 遵循 Goz 的建议是一个更简单/更好的选择,但如果您绝对必须使用数组,那么您可能必须使用 placement new;这不是一个明智的选择。
  • @JoachimPileborg thx 对于指针,我意识到我只阅读了第一行 complex( const T& re = T(), const T& im = T() );并假设 double() 因此未初始化。我觉得下面的特殊情况有点不一致,但必须忍受。很遗憾,因为这种开销渗透到我所有的库中,包括变量定义等。将不得不一一解决它们:-/
  • double() 未初始化。
  • 我错过了什么吗?专门化的存在只是为了允许按值传递双精度 - 它与非专门化构造函数具有相同的语义 - double() 等于 0.0

标签: c++ c++11 default-constructor


【解决方案1】:

我想知道为什么 g++ 费心用零初始化 std::complex

因为标准规定必须这样做,所以默认构造函数声明为:

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

所以它将两个成员都设置为零。

标准库安全地初始化类型是正常的,而不是像 doubleint* 这样的内置类型,例如 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 添加到转换构造函数和/或转换运算符以适合您的口味。

【讨论】:

  • 好的,但这很荒谬,因为 'double' 和 'int' 是内置的,而 std::complex 只不过是它们的扩展。性能影响不容忽视,特别是在计算密集型应用程序中,我向您保证,如果需要,它们总是会注意初始化它们的变量。
  • 不,这不是扩展。作为语言语法本身的一部分(内置)与使用语言创建的类型(用户定义的类型)不同。从语言的角度来看,它只是一个class,除此之外无法做出任何假设。它的行为取决于类型的设计者。
  • @Casey 不,D 的默认构造函数是用户提供的,因此不会进行零初始化。 Your assertion will fire on clang.
  • 使用默认值(如零)比不初始化它们更安全。最好是在调试模式下初始化为随机垃圾。危险在于无缘无故的零会掩盖错误 - 直到它没有。
  • 更好(但仍然很糟糕)的替代方法是将双打初始化为 NAN(非数字)。开发人员或维护人员将更有可能在产品发布之前发现 un-init 错误。
【解决方案2】:

有一种简单的方法可以做到这一点。如果您使用 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' 将在其超出范围时自动释放。

【讨论】:

  • 我正在构建自己的数值结构 (github.com/juanjosegarciaripoll/tensor),我宁愿不在 std::vector 之上构建它们,它有自己的开销,这在进行大量矩阵乘法时变得很重要之类的。我想要的是复制'vector的行为w.r.t。未初始化的内存,现在我看到 C++ 委员会通过过度指定 std::complex 犯了一个愚蠢的错误。
  • @Juanjo:malloc 和 free 不会调用构造函数,但它不是真正的 C++ 解决方案!
  • @legends2k 我说“愚蠢”是因为它与所有其他未初始化的实数的行为不一致,并且因此引入了不可避免且无用的开销。我不确定放置 new 是否可以,因为它仍然调用构造函数parashift.com/c++-faq/placement-new.html
  • @Juanjo,使用placement new 的要点是,在您知道要使用什么值对其进行初始化之前,您不会构造对象,因此它不会进行不必要的零初始化-前面。
  • @Goz 怎么样?派生的(来自std::complex)必须构造它的基础,所以你不能避免调用std::complex的一些构造函数。
【解决方案3】:

以下来自我正在开发的代码。

稍后添加:作为 M.M.在 cmets 中指出,该行为在技术上是未定义的。现在我看到,如果一些卑鄙的黄鼠狼要改变 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); }
};

【讨论】:

  • 这是未定义的行为:您正在访问其中没有对象的存储,就好像它有对象一样。见stackoverflow.com/questions/40873520。即使该线程上的 POD 的情况也不适用于此处,因为 std::complex 具有非平凡的初始化。
  • @M.M.我无法想象这将如何失败。我不打算深入挖掘律师界。如果你说它是未定义的,我相信。那么,该怎么做呢? std::complex 如果所有这些无偿的零都是交易破坏者,那么对于语言学究来说是无用的吗?妈妈该怎么办?请发布答案。
  • @M.M 我应该问一下,如果不能容忍零,std::complex 是否没用。? double 类型的特化是皇家 PITA。
  • 这很糟糕,因为它假定 sizeof(aligned_storage&lt;sizeof(complex)&gt;) == sizeof (complex) 不能保证
  • 如果您可以安排代码使用placement-new进行写入,并确保您在至少一次写入之前从未阅读过,那将是一种方法...(可以使用placement-new多次次,因为 complex 没有析构函数的副作用)否则您可能必须滚动自己的复数类以避免零初始化。我同意其他地方表达的观点,即complex&lt;double&gt; 的默认构造函数违背了通常的 C++ 原则“只为你使用的东西付费”。我可以稍后将此评论变成答案
猜你喜欢
  • 1970-01-01
  • 2016-12-29
  • 1970-01-01
  • 2017-05-02
  • 2015-09-04
  • 2020-05-05
  • 1970-01-01
  • 2019-11-23
  • 2020-09-03
相关资源
最近更新 更多