有没有办法实现移动省略

时间:2017-07-04 12:43:57

标签: c++ copy-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,它会被构建到位,但通过我的测试,似乎并非如此。

3 个答案:

答案 0 :(得分:3)

  

反正移动构造函数有没有? [...]我希望用-O3,它将被构建到位,

嗯,你明确地调用了移动构造函数......并且对象正在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,那么您的问题会更有意义。 E.g:

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

不调用移动/复制构造函数。

live example on wandbox

答案 1 :(得分:2)

我认为你不能这样做。因为elision要求编译器具有构造对象的内在知识。有了这些信息,它可以避免移动和复制,只需将对象放在需要的位置即可。例如,当您将某个函数的堆栈中的某些内容返回给另一个函数时,编译器可以忽略移动/复制。

但在你的情况下,placement new允许你将对象构造成任意缓冲区。并且该缓冲区实际上可以在任何地方,例如它可以在堆栈上(如您的情况)或者可以使用new在堆上分配。所以编译器不会在这里忽略移动/复制。

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

注意请注意,您已经声明了一个字符指针数组而不是字符,所以如果这是您期望的那样,缓冲区的长度超过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)...};
}

答案 2 :(得分:1)

使用具有完美参数转发的emplace函数。还有一些事情需要说明,因为您即将踏上冒险之旅:

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

  2. 请阅读并使用展示位置new的返回值。在简单的情况下,它与您提供的地址不同。但是,标准允许它,并且在复杂的类层次结构中它可能是。

  3. 更新示例:

    #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;
    }