【问题标题】:Is there any way to achieve move elision有什么方法可以实现移动省略
【发布时间】:2017-12-07 22:46:12
【问题描述】:

运行以下代码时:

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 可以就地构建它,但根据我的测试,情况似乎并非如此。

【问题讨论】:

    标签: c++ copy-elision


    【解决方案1】:

    有没有办法省略移动构造函数? [...] 我希望使用 -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,您的问题会更有意义。例如:

    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

    【讨论】:

    • 谢谢,是的,你完全正确,std::move 引起了一些问题。问题是,没有它,它会调用复制运算符,并且不会在任何 -O 级别被省略。
    • @ioquatix: std::move 并没有真正引起任何问题 - 我不明白你想要做什么 - 你明确地调用了 Copy... 的构造函数...但期待不叫吗?
    • 我希望编译器会忽略它。
    • @ioquatix:您基本上是在要求编译器在此处省略对foo() 的调用void foo(); int main(){ foo(); }。只需将void foo() 视为Copy::Copy(Copy&amp;&amp;)。您正在显式调用该构造函数,该构造函数正在执行副作用。它怎么可能被忽略......为什么?
    • 坦率地说,关于省略的规则以及如何处理它并不容易理解。我很感激你给了我一些遵循/思考的指导方针。通过使用您的建议,我设法在我的特定情况下使用单个移动构造函数。您对自己的时间和耐心非常慷慨。
    【解决方案2】:

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

    但在您的情况下,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)...};
    }
    

    【讨论】:

    • 哈哈 - 当我看到 char * 缓冲区 [1024] 时我笑了。这很有趣 :D 感谢所有其他信息。是的,我也试过不使用std::move,但没有任何区别。
    【解决方案3】:

    使用具有完美参数转发的 emplace 函数。既然您即将踏上新安置的冒险之旅,还有一些话要说:

    1. 使用std::aligned_storage_t&lt;&gt; 进行存储。它保证您的对象将正确对齐。

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

    更新示例:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多