调用采用非常量指针参数的 c 函数的正确方法,const_cast、reinterpret_cast、launder
Proper way to call a c-function taking non-const pointer arguments, const_cast, reinterpret_cast, launder
从 C++ 调用采用非常量自定义指针参数的 c 函数的正确方法是什么?
以 FFTW3 中的函数 fftw_plan_dft_1d
为例,作为一个非常常见的例子。 http://fftw.org/fftw3_doc/Complex-DFTs.html#Complex-DFTs
fftw_plan fftw_plan_dft_1d(int n0,
fftw_complex *in, fftw_complex *out,
int sign, unsigned flags);
(fftw_complex
是 double[2]
的类型定义)。
假设我想将此函数应用于几个常量正确的 C++ 容器。
std::vector<std::complex<double>> const In = {...};
std::vector<std::complex<double>> Out(In.size());
我应该怎么做?
_第一次迭代,我必须从容器中提取数据指针,
assert(In.size() == Out.size());
fftw_plan fftw_plan_dft_1d(In.size(),
In.data(), Out.data(), // error const
FFTW_FORWARD, FFTW_ESTIMATE);
_第二次迭代
但由于它是 const,所以我必须进行 constcast。
我假设这是唯一可能的解决方案,假设 C 接口的原因是 C 没有常量参数。
fftw_plan p = fftw_plan_dft_1d(In.size(),
const_cast<std::complex<double>*>(In.data()), // error std::complex is not convertible to fftw_complex
Out.data(),
FFTW_FORWARD, FFTW_ESTIMATE);
_第三次迭代
现在,我必须将 std::complex<double>
转换为 fftw_complex
(double[2]
)。幸运的是 std::complex<double>
需要与 double[2]
.
具有相同的布局
fftw_plan p = fftw_plan_dft_1d(In.size(),
reinterpret_cast<fftw_complex*>(const_cast<std::complex<double>*>(In.data())), // is this UB?
reinterpret_cast<fftw_complex*>(Out.data()),
FFTW_FORWARD, FFTW_ESTIMATE);
现在我很偏执,显然 reinterpret_cast
总是 UB。
我不知道如何使用 std::launder
但我知道在某些情况下它可以节省 reinterpret_cast
UB。
fftw_plan p = fftw_plan_dft_1d(In.size(),
std::launder(reinterpret_cast<fftw_complex*>(const_cast<std::complex<double>*>(In.data()))), // needs c++17
std::launder(reinterpret_cast<fftw_complex*>(Out.data())),
FFTW_FORWARD, FFTW_ESTIMATE);
归根结底,这是调用涉及 const 和类型重新解释的 C 函数的合理方法吗?
我是不是太偏执了?或者只是在这种情况下从 C++ 调用 C 总是正式的 UB 而我对此无能为力?
我觉得你确实比较偏执,我也觉得这是好事。保持。有点偏执,搬起石头砸自己的脚的次数会大大减少!
您正确地确定了需要放弃 const
限定符,因为该库在其函数签名中没有使用 const
。并且您使用 const_cast<>
.
正确识别了解决方案
您还正确地确定 reinterpret_cast<>
在这种情况下技术上 UB if 你不假设 fftw_complex
被定义为 double[2]
。 (我不熟悉 FFTW3,所以我不知道那是不是真的,但你可能知道。)如果你知道它是一个 typedef,它就不是 UB,因为类型相同,只是别名为下不同的名字。如果你不知道,它 "probably" 仍然是安全的,但是,是的,我认为这可能是你必须做出一点信念飞跃的情况,知道任何理智的,现实世界的编译器应该做正确的事情。 FFTW3 文档中有 note to this effect。
C++ has its own complex template class, defined in the standard header file. Reportedly, the C++ standards committee has recently agreed to mandate that the storage format used for this type be binary-compatible with the C99 type, i.e. an array T[2] with consecutive real [0] and imaginary [1] parts. (See report http://www.open-std.org/jtc1/sc22/WG21/docs/papers/2002/n1388.pdf WG21/N1388.) Although not part of the official standard as of this writing, the proposal stated that: “This solution has been tested with all current major implementations of the standard library and shown to be working.” To the extent that this is true, if you have a variable complex *x, you can pass it directly to FFTW via reinterpret_cast(x).
(当然 this layout guarantee 现在是 C++11 标准的一部分。)
最后,关于 C++ 风格转换的说明。每个人都说你应该使用它们而不是 C 转换,在大多数情况下都是如此,但是 C 风格转换 定义明确并且如果你使用它们,程序不会崩溃。权衡是简洁和可读代码(C 风格)与显式意图声明(C++ 风格)之间的权衡。 C++ 编译器使用 C 风格强制转换的确切规则 are defined here。在我个人看来,由于您已经在处理函数签名不尽如人意的 C 库,因此简单地将 C 样式强制转换为 (double *)
并结束它并不是世界末日.
从 C++ 调用采用非常量自定义指针参数的 c 函数的正确方法是什么?
以 FFTW3 中的函数 fftw_plan_dft_1d
为例,作为一个非常常见的例子。 http://fftw.org/fftw3_doc/Complex-DFTs.html#Complex-DFTs
fftw_plan fftw_plan_dft_1d(int n0,
fftw_complex *in, fftw_complex *out,
int sign, unsigned flags);
(fftw_complex
是 double[2]
的类型定义)。
假设我想将此函数应用于几个常量正确的 C++ 容器。
std::vector<std::complex<double>> const In = {...};
std::vector<std::complex<double>> Out(In.size());
我应该怎么做?
_第一次迭代,我必须从容器中提取数据指针,
assert(In.size() == Out.size());
fftw_plan fftw_plan_dft_1d(In.size(),
In.data(), Out.data(), // error const
FFTW_FORWARD, FFTW_ESTIMATE);
_第二次迭代
但由于它是 const,所以我必须进行 constcast。
我假设这是唯一可能的解决方案,假设 C 接口的原因是 C 没有常量参数。
fftw_plan p = fftw_plan_dft_1d(In.size(),
const_cast<std::complex<double>*>(In.data()), // error std::complex is not convertible to fftw_complex
Out.data(),
FFTW_FORWARD, FFTW_ESTIMATE);
_第三次迭代
现在,我必须将 std::complex<double>
转换为 fftw_complex
(double[2]
)。幸运的是 std::complex<double>
需要与 double[2]
.
fftw_plan p = fftw_plan_dft_1d(In.size(),
reinterpret_cast<fftw_complex*>(const_cast<std::complex<double>*>(In.data())), // is this UB?
reinterpret_cast<fftw_complex*>(Out.data()),
FFTW_FORWARD, FFTW_ESTIMATE);
现在我很偏执,显然 reinterpret_cast
总是 UB。
我不知道如何使用 std::launder
但我知道在某些情况下它可以节省 reinterpret_cast
UB。
fftw_plan p = fftw_plan_dft_1d(In.size(),
std::launder(reinterpret_cast<fftw_complex*>(const_cast<std::complex<double>*>(In.data()))), // needs c++17
std::launder(reinterpret_cast<fftw_complex*>(Out.data())),
FFTW_FORWARD, FFTW_ESTIMATE);
归根结底,这是调用涉及 const 和类型重新解释的 C 函数的合理方法吗?
我是不是太偏执了?或者只是在这种情况下从 C++ 调用 C 总是正式的 UB 而我对此无能为力?
我觉得你确实比较偏执,我也觉得这是好事。保持。有点偏执,搬起石头砸自己的脚的次数会大大减少!
您正确地确定了需要放弃 const
限定符,因为该库在其函数签名中没有使用 const
。并且您使用 const_cast<>
.
您还正确地确定 reinterpret_cast<>
在这种情况下技术上 UB if 你不假设 fftw_complex
被定义为 double[2]
。 (我不熟悉 FFTW3,所以我不知道那是不是真的,但你可能知道。)如果你知道它是一个 typedef,它就不是 UB,因为类型相同,只是别名为下不同的名字。如果你不知道,它 "probably" 仍然是安全的,但是,是的,我认为这可能是你必须做出一点信念飞跃的情况,知道任何理智的,现实世界的编译器应该做正确的事情。 FFTW3 文档中有 note to this effect。
C++ has its own complex template class, defined in the standard header file. Reportedly, the C++ standards committee has recently agreed to mandate that the storage format used for this type be binary-compatible with the C99 type, i.e. an array T[2] with consecutive real [0] and imaginary [1] parts. (See report http://www.open-std.org/jtc1/sc22/WG21/docs/papers/2002/n1388.pdf WG21/N1388.) Although not part of the official standard as of this writing, the proposal stated that: “This solution has been tested with all current major implementations of the standard library and shown to be working.” To the extent that this is true, if you have a variable complex *x, you can pass it directly to FFTW via reinterpret_cast(x).
(当然 this layout guarantee 现在是 C++11 标准的一部分。)
最后,关于 C++ 风格转换的说明。每个人都说你应该使用它们而不是 C 转换,在大多数情况下都是如此,但是 C 风格转换 定义明确并且如果你使用它们,程序不会崩溃。权衡是简洁和可读代码(C 风格)与显式意图声明(C++ 风格)之间的权衡。 C++ 编译器使用 C 风格强制转换的确切规则 are defined here。在我个人看来,由于您已经在处理函数签名不尽如人意的 C 库,因此简单地将 C 样式强制转换为 (double *)
并结束它并不是世界末日.