【问题标题】:Wrap a C++ object without move constructor in a unique_ptr?在 unique_ptr 中包装没有移动构造函数的 C++ 对象?
【发布时间】:2021-06-23 16:15:25
【问题描述】:

所以我对移动语义和现代 C++ 很陌生,但据我了解,我可以编写类似的代码

Foo doSomething(Bar b) {
    Foo f{b};
    return f;
}

请注意,我没有明确编写 std::move 以允许编译器应用 RVO。

我不想修改 Foo 或 Bar 并且编译器失败并显示 Call to implicitly-deleted copy constructor of 'Foo'

所以我试图通过引入一个动作来解决这个问题:

Foo doSomething(Bar b) {
    Foo f{b};
    return std::move(f);
}

失败并出现同样的错误。 (额外的问题:当我想移动 Foo 时,为什么编译器会抱怨复制构造函数?)

所以我的想法是将 Foo 包装在 std::unique_ptr 中,我的问题是这是否是修复它的“正确”方法。

std::unique_ptr<Foo> doSomething(Bar b) {
    auto f = std::make_unique<Foo>(b);
    return f;
}

【问题讨论】:

  • *NRVO。此外,Foo 显然没有 cctor 和 mctor。这样的对象可以通过 RVO 的奇迹返回(或者更准确地说 -- prvalue 在 C++17 中强制执行的魔法)
  • 您正在询问返回Foo 的适当方式。这取决于Foo 是什么。这是您创建的课程吗?我们只能从您的错误中推断出它是一种隐式删除其复制构造函数的类型。
  • 是的,您可以通过添加(并支付)间接级别来解决很多问题。
  • @DrewDormann 公平点 - 实际上我对 Foo 也不太了解,除了你提到的一点

标签: c++


【解决方案1】:

据我了解。

传统上,返回值优化就是这样一种优化(尽管它可能会改变程序的行为)。所以复制构造函数必须可用(而不是“删除”),即使编译器最终决定不需要使用它。

C++11 引入了移动语义,其中一部分是允许在返回值时使用移动构造函数的特殊规则。因此,您可以返回可以移动但不能以正常方式复制的类型,而无需显式使用 std::move。

C++17 更改了匿名返回值的规则,以保证它们是无复制的*,并且不再需要复制或移动构造函数。然而,它并没有解决命名返回值这一更复杂的问题。

有一个解决命名返回值问题的建议 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2025r0.html),但我不知道它是否会被接受。

包装在 unique_ptr 中确实是一种有效地将不可移动对象转换为可移动对象的方法,但这也意味着对象存储在堆上而不是堆栈上。在堆上存储对象有利有弊,一方面,在大多数系统上,堆空间通常比可用的堆栈空间多,另一方面,在堆上分配和释放内存比在堆栈上更昂贵,并且额外的间接级别也有一些成本。

额外问题:当我想移动 Foo 时,为什么编译器会抱怨复制构造函数?

std::move 实际上并没有移动任何东西,它只是将左值引用更改为右值引用。如果存在移动构造函数,则 std::move 将导致使用它来支持复制构造函数,但如果不存在移动构造函数,std::move 不会停止使用复制构造函数。

* 出于 ABI 兼容性(以及潜在的性能)原因,此规则有一个例外,即可简单复制的类型。

【讨论】:

    【解决方案2】:

    额外问题:当我想移动 Foo 时,为什么编译器会抱怨复制构造函数?

    当 Foo 既不可移动也不可复制时,编译器可能会这样做。

    所以我的想法是将 Foo 包装在 std::unique_ptr 中,我的问题是这是否是修复它的“正确”方法。

    您可能应该返回一个纯右值:

    Foo doSomething(Bar b) {
        return Foo{b};
    }
    

    请注意,如果 Foo 不能移动,这需要 C++17 才能工作。在 C++17 之前,“正确”的方式取决于上下文。唯一指针可能没问题,或者一开始就不写函数。

    【讨论】:

    • 但是如果我的代码首先构造 Foo,然后在上面做一些事情(比如baz(f))然后返回 f?
    • @KillPinguin 那就不要写这样的函数了。将Foo 的创建与baz 的调用分开。
    • 如果 baz 实际上与 Foo 的创建有关?像初始化 Foo 的方法一样?
    • @KillPinguin 然后baz 应该返回Foo 而不是接受它作为参数。或者它应该是一个构造函数。
    猜你喜欢
    • 2013-01-10
    • 1970-01-01
    • 2013-03-27
    • 2015-05-25
    • 2017-09-24
    • 1970-01-01
    • 2017-09-19
    • 2019-05-17
    • 1970-01-01
    相关资源
    最近更新 更多