C ++ 17引入了guaranteed copy elision,但目前尚未为some compilers实现该功能(特别是英特尔C ++ 17和18)。
我目前有一个使用英特尔C ++ 17编译的SIMD程序,其中复制/移动省略没有被优化掉,从而导致SIMD序列化为首先无用的分配(进一步导致不可忽视的性能损失)。 / p>
我的程序看起来像这样:
class X { ... };
struct S {
// default ctor, default copy/move ctor/assignement
// and recursivly so for all data members
// no virtual member functions
...
constexpr
S(const X& x) : ... {}
constexpr auto
update(const X& x) {
...
}
};
constexpr auto
make_S(const X& x) { // note : except for being templated, make_S is not simplified from my real implementation.
// I only want to use it for overloading, allowing the caller to not specify the actual type of S
return S(x); // Yes, that simple :'(
}
constexpr auto
init_S(S& s, const X& x) {
s.update(x);
}
int main() {
X x;
S s(x); // copy elision success
// vs
auto s = make_S(x); // icc does not elide the move
// vs
S s;
init_S(s,x); // no copy
return 0;
}
据我所知,C ++ 17需要复制省略,事实上,对于删除的移动/复制ctors,gcc 7.2和-std = c ++ 17接受代码。但icc 17,即使使用-std = c ++ 17也不会忽视复杂情况。
有没有办法确保在英特尔C ++上进行复制省略优化?据我所知,优化大多数时间都在进行,但是一些复杂的情况在没有明显原因的情况下失败。副本缺失失败的原因是什么?
注意:所有函数都是inline
,并由编译器内联。
附带问题:在C ++ 14时代,在强制要求之前复制省略优化有多好?我似乎记得clang同样没有优化它可能拥有的一切。
答案 0 :(得分:1)
optional
方式如果您有C++17
最佳选项,可确保不进行复制,并且在函数内创建对象为std::optional
(或boost:optional
)。谢谢@Barry,你让我完美的小课程过时了:))
struct X
{
X(int, int);
X(const X&) = delete;
X(X&&) = delete;
};
auto foo_no_copy(std::optional<X>& xo)
{
X& x = xo.emplace(11, 24);
// use x
}
auto test()
{
std::optional<X> xo;
foo_no_copy(xo);
// use *xo
}
有时最简单的解决方案是正确的解决方案。
根据您的情况,其他解决方案可能过度。如果你可以在函数之外构造你的对象而没有任何性能损失,那么你所要做的就是传递一个引用:
auto foo(X& x) -> void;
auto test()
{
X x{};
foo(x);
// use x
}
Copy_elision
包装器如果您无法访问optional
课程,则以下是我的原始答案:
如果你真的必须确保复制省略机制,你可以在高级别模拟编译器在低级别上做什么:在调用者上分配内存并将指针传递给被调用函数。然后被调用的函数将使用该内存来构建一个放置new的对象。
我为此创建了一个类型安全的RAII包装类:
template <class X>
struct Copy_elision
{
std::aligned_storage_t<sizeof(X)> storage_{};
X* x_ = nullptr;
public:
Copy_elision() = default;
Copy_elision(const Copy_elision&) = delete;
Copy_elision(Copy_elision&) = delete;
auto operator=(const Copy_elision&) -> Copy_elision& = delete;
auto operator=(Copy_elision&&) -> Copy_elision& = delete;
~Copy_elision()
{
if (x_)
x_->~X();
}
template <class... Args>
auto construct(Args&&... args) -> X&
{
x_ = new (&storage_) X{std::forward<Args>(args)...};
return *x_;
}
auto get() const -> X* { return x_; }
};
以下是如何使用它:
auto foo_no_copy(Copy_elision<X>& xce) -> X&
{
X& x = xce.construct(11, 24);
// work with x
return x;
}
auto test()
{
Copy_elision<X> x_storage;
X& x = foo_no_copy(x_storage);
// work with x
}
如果您很好奇,这里是没有包装类的Copy_elision
版本。我强烈建议不要使用这个版本,因为它非常脆弱:
#include <memory>
#include <type_traits>
struct X
{
X(int, int);
X(const X&) = delete; // can't copy
X(X&&) = delete; // can't move
};
auto foo_no_copy(void* xp/* , other params*/) -> X&
{
X& x = *(new (xp) X{12, 24});
// work with x
return x;
}
auto test()
{
std::aligned_storage_t<sizeof(X)> x_storage;
X& x = foo_no_copy(&x_storage);
// work with x
x.~X();
}