有没有办法实现移动省略

Is there any way to achieve move elision

当运行如下代码:

struct Copy
{
    Copy() {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
    Copy(const Copy & other) {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
    Copy & operator=(const Copy & other) {std::cerr << __PRETTY_FUNCTION__ << std::endl; return *this;}
    Copy(Copy && other) {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
    Copy & operator=(Copy && other) {std::cerr << __PRETTY_FUNCTION__ << std::endl; return *this;}
    ~Copy() {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
};

char buffer[1024];

template <typename Type>
Type * push(Type value)
{
    return new(buffer) Type(std::move(value));
};

int main()
{
    push(Copy());

    return 0;
}

我得到以下输出:

Copy::Copy()
Copy::Copy(Copy &&)
Copy::~Copy()

有没有办法省略移动构造函数?

我希望使用 -O3 时,它会就地构建,但根据我的测试,情况似乎并非如此。

Is there anyway to elide the move constructor? [...] I was hoping that with -O3, it would be constructed in place,

好吧,您正在显式调用移动构造函数...并且正在 buffer 中就地构造对象。为什么您希望省略移动构造函数调用?

template <typename Type>
Type * push(Type value)
{ 
    //                 invokes `Type::Type(Type&&)`
    //                 vvvvvvvvvvvvvvvvvvvvvv
    return new(buffer) Type(std::move(value));
    //                      ^^^^^^^^^^^^^^^^
    //                      casts `value` to `Type&&`
};

如果您尝试使用其他类型的值构造 Copy,您的问题会更有意义。例如:

struct Copy
{
    Copy(int) { std::cout << "INT\n"; }
    // ... other ctors ...
};

template <typename Type, typename Arg>
Type * push(Arg x)
{
    return new(buffer) Type(std::move(x));
};

int main()
{
    push<Copy>(1);
}

上面的代码打印:

INT

未调用 move/copy 构造函数。

live example on wandbox

我认为你做不到。因为省略要求编译器对对象的构造位置有内在的了解。有了这些信息,它就可以避免移动和复制,只需将对象放在需要去的地方。例如,当您 return 从一个函数的堆栈返回到另一个函数时,编译器可以省略 move/copy。

但在您的情况下,placement new 允许您将对象构造到任意缓冲区中。该缓冲区实际上可以在任何地方,例如它可以在堆栈上(就像您的情况一样)或者可以使用 new 在堆上分配。所以编译器不会省略这里的move/copy。

严格来说,这可以通过对代码的一些分析来实现,因为编译器已经知道缓冲区在哪里,但我怀疑大多数编译器是否实现了这一点。


注意注意你已经声明了一个字符指针数组而不是字符数组,所以缓冲区的长度超过 1024 字节,如果这是你所期望的


备注Also note that calling std::move explicitly can prevent elision


在这种情况下,您可以做的最好的事情是创建一个就地构造函数或一个移动构造函数(如上所示)将该对象构造到缓冲区中。就地构造函数看起来像这样

template <typename... args>
void construct(std::in_place_t, Args&&... args) {
    new (buffer) Type{std::forward<Args>(args)...};
}

使用具有完美参数转发的 emplace 函数。既然你即将开始新的安置冒险,还有一些话要说:

  1. 使用std::aligned_storage_t<>进行存储。它保证您的对象将正确对齐。

  2. 阅读并使用放置 new 的 return 值。在简单的情况下,它与您提供的地址没有什么不同。然而标准允许它是,并且在复杂的 class 层次结构中它可能是。

更新示例:

#include <iostream>

struct Copy
{
    Copy() {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
    Copy(const Copy & other) {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
    Copy & operator=(const Copy & other) {std::cerr << __PRETTY_FUNCTION__ << std::endl; return *this;}
    Copy(Copy && other) {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
    Copy & operator=(Copy && other) {std::cerr << __PRETTY_FUNCTION__ << std::endl; return *this;}
    ~Copy() {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
};

std::aligned_storage_t<sizeof(Copy), alignof(Copy)> buffer[4];

template <typename Type, typename LegalStorage, typename...Args>
auto emplace(LegalStorage* buffer, Args&&...args) -> Type*
{

    return new(buffer) Type(std::forward<Args>(args)...);
};

int main()
{
    auto p1 = emplace<Copy>(buffer /* constructor arguments go here*/);
    auto p2 = emplace<Copy>(&buffer[1]/* constructor arguments go here*/);
    auto p3 = emplace<Copy>(buffer + 2/* constructor arguments go here*/);
    auto p4 = emplace<Copy>(buffer + 3/* constructor arguments go here*/);

    return 0;
}