【问题标题】:Move constructor involving const unique_ptr移动涉及 const unique_ptr 的构造函数
【发布时间】:2015-05-25 12:02:38
【问题描述】:

在下面的代码中,我将 p 设置为 const,因为它在 Foo 的生命周期内永远不会指向任何其他 int。这不会编译,因为调用了 unique_ptr 的复制构造函数,这显然被删除了。除了使 p 非常量之外,还有其他解决方案吗?谢谢。

#include <memory>

using namespace std;

class Foo 
{
public:
  //x is a large struct in reality
  Foo(const int* const x) : p(x) {};
  Foo(Foo&& foo) : p(std::move(foo.p)) {};
private:
  const unique_ptr<int> p;
};

【问题讨论】:

  • 您究竟需要const unique_ptr 做什么?也许你想要一个 shared_ptr 代替?
  • 我不“需要”它,但我想指出 p 将在 Foo 的生命周期中指向同一个 int。
  • 如果p 永远不会指向另一个int 那么为什么要使用指针而不是普通成员?
  • 在我的实际代码中,“x”是一个大的 pod 结构。我宁愿传递一个指针而不是复制/移动它。
  • 您可以将其设为 const 并且不允许 Foo 移动,或者将其设为非 const。对不起。

标签: c++ constants move unique-ptr move-constructor


【解决方案1】:

移动构造函数的语义是矛盾的。

您已经声明了一个const std::unique_ptr,它将(唯一地)拥有初始化它的值。 但是您已经声明了一个移动构造函数,该构造函数应该在构造时将该值移动到另一个对象中。

那么您认为“临时”构造中的 std::unique_ptr 应该发生什么?

如果您希望它成为release()ed,您就违反了它的constness。 如果您希望它保留其值,则您违反了std::unique 的约束,要求不超过一个这样的对象来拥有任何给定的对象。 将死。

这个问题揭示了 C++ 语言的一个微妙限制。它需要move 语义才能将复制到作为有效对象。

对于“破坏性移动”有几个相当合理的建议,实际上可以更好地反映move 的大多数用途正在做什么——从那里“使”那里的内容“无效”。

谷歌他们。我没有做过文献调查,所以不想推荐。

您在此处的替代方法是删除 const 或强制转换。 我强烈建议删除它。您可以确保类的语义确保适当的 const-ness 没有影响,也没有“丑陋的嫌疑人”const_cast

#include <iostream>
#include <memory>

class Foo 
{
public:
  Foo(const int x) : p(new int(x)) {};
  Foo(Foo&& foo) :
    p(std::move(foo.p)) {

    };

    int get(void)const{
        return *(this->p);
    }

private:
     std::unique_ptr<int> p;
};

Foo getMove(){
    return Foo(88);
}

int main(){

    Foo bar(getMove());    
    std::cout<<bar.get()<<std::endl;

    return EXIT_SUCCESS;
}

【讨论】:

  • 我将如何使用const_cast?我试过const_cast,但收到一个编译错误:无效使用类型为'std::unique_ptr'的const_cast,它不是指针、引用,也不是指向数据成员的指针
  • 我不宽恕它,但您正在寻找的代码是 p(std::move(const_cast&lt;std::unique_ptr&lt;int&gt;&amp; &gt;(foo.p))) 在移动构造函数的初始化程序中。
  • 谢谢,这是我在这个线程中寻找的一行代码(我明白为什么它没有从一开始就编译)。
  • @AgrimPathak:很高兴为您提供帮助。我想。我认为在移动语义中违反对象的约束在技术上是可以的,只要它留下一个可破坏的对象。
【解决方案2】:

要了解您的代码无法编译的原因,请反映您如何声明 Foo 类以及通常如何实现 move semantics

声明const unique_ptr&lt;T&gt; p,意味着p本身永远不会被修改,但你仍然可以修改指向的对象,因为T不是const。

但是move 在相反的假设下工作。此功能使用允许从对象中窃取资源并将它们保持在空状态(如果空状态有意义)的想法。如果有用,请将move 视为移动对象的一种“破坏性”副本。

std::move(foo.p),基本上就是盗取foo.p指向的资源并让它处于安全状态,也就是说将foo.p分配给NULL。但是foo.p被声明为const,所以不允许操作。

请注意,在您的情况下,您不需要将 p 声明为 const unique_ptr&lt;int&gt;。只需将其声明为 unique_ptr&lt;int&gt; 并确保成员函数声明为 const 并且非成员函数将其作为 const unique_ptr&lt;int&gt; p&amp; 参数。通过这种方式,您可以确定 p 在对象生命周期内永远不会更改(移动操作除外)。

【讨论】:

    【解决方案3】:

    这是因为unique_ptr只有move-constructor,这意味着p的初始化参数不能是const,而p是const。我想你想要的是声明

    unique_ptr p;

    而不是

    const unique_ptr p;

    class Foo {
      public:
        // x is a large struct in reality
        Foo(const int* const x) : p(x) {};
        Foo(Foo&& foo) : p(std::move(foo.p)) {};
      private:
        const unique_ptr<int> p;
    };
    

    【讨论】:

      【解决方案4】:

      使用 std::unique_ptr 的概念是表示对象的唯一所有权。您想要实现的是让 Foo 类拥有一个对象(由 std::unique_ptr 表示)使其可移动(您的移动构造函数),这会产生矛盾。我会坚持使用 std::unique_ptr 或使用 std::shared_ptr 共享它。

      您可能想阅读以下内容: Smart Pointers: Or who owns you baby?

      【讨论】:

      • 如何产生矛盾?是搬家,不是抄袭?
      • 矛盾是对象的唯一所有权并移动它 - 在实例化类 Foo 的对象后它拥有一个“p” - 它以后不能移动并导致另一个对象拥有“p”,因为它归前一个对象所有。
      【解决方案5】:

      如果您想防止所有权转移,您可以使用const std::unique_ptr&lt;T&gt;。这不是很有用。

      如果你想防止修改它持有的对象,你可以使用std::unique_ptr&lt;const T&gt;

      【讨论】:

        猜你喜欢
        • 2017-09-24
        • 2020-09-22
        • 1970-01-01
        • 2013-03-27
        • 2019-05-17
        • 1970-01-01
        • 1970-01-01
        • 2011-09-13
        • 1970-01-01
        相关资源
        最近更新 更多