在 c++11 中定义仅移动对象有意义吗?

Is there a point to define move-only objects in c++11?

提问人:Govan 提问时间:10/24/2015 最后编辑:CommunityGovan 更新时间:8/24/2018 访问量:10158

问:

我之前有一个关于使用 unique-ptrs 的问题。我得到这个答案,建议使用仅移动对象。我定义了一个类,如下所示:

class B {
    const string objName;
public:

    B ( B && ) = default;
    B &  operator= ( B && ) = default;
    B ( const B & ) = delete;
    B & operator= ( const B & ) = delete;

    B(const string & name) :
            objName(name) {

    }

    virtual ~B();

    const string name() const { return objName;};
}

我用这句话称呼 B:

class A {
A(){}
void take(B b);
}

A a; 
B b("test");
cout<<b.name();
a.take(std::move(b));
cout<<b.name();

我的问题:

  1. 即使我默认了移动构造函数,我也无法编写 a.take(b) 并且出现编译错误。我知道复制结构被删除了,但似乎合乎逻辑的选择是在默认时使用 move 构造函数,而无需像这样编写 std::move a.take(b)
  2. 在结果中,“测试”被打印两次。为什么调用 move 后 b 对象没有被移除?如果 b 对象仍然存在,并且它的副本已发送到 a.take(move(b)),则意味着我们对右值对象没有任何使用 move。
  3. 使用上述仅移动对象(删除复制构造函数和赋值运算符以及默认的移动构造函数和移动赋值)是否是一种好的做法?
C++ C++11 标准 智能指针 按值传递

评论

1赞 Alan Stokes 10/24/2015
尝试将“test”替换为更长的字符串,看看会发生什么。通常,从中移出的对象的值是不确定的;它可能是旧值,也可能不是旧值。
4赞 Alan Stokes 10/24/2015
如果要从中移动,则需要删除 from。constobjName
0赞 Govan 10/24/2015
@AlanStokes删除 const 将导致该测试将打印一次。这是否意味着 const memebers 永远不会被移动?还是会被复制?
1赞 Alan Stokes 10/24/2015
它们无法从中移动,因此必须复制它们。
0赞 vsoftco 10/25/2015
仅移动对象的典型示例是 ,它非常有用。std::unique_ptr

答:

7赞 tweej 10/24/2015 #1

是的,有一点。管理资源(也许是物理资源)的对象不能/不应该在对象之间共享,这是我想到的第一个例子。

1)你写错了。这是我认为你根据这个问题和你之前的问题想要的。

class B {
    std::string objName;
public:
    B ( B && ) = default;
    B &  operator= ( B && ) = default;
    B ( const B & ) = delete;
    B & operator= ( const B & ) = delete;
    B(const std::string & name) :
            objName(name) {}
    virtual ~B() {}
    std::string name() const { return objName;}
};

class A {
public:
   std::vector<B> v;
   void take(B && b)
   {
      v.push_back(std::move(b));
   }
};

int main()
{
   A a;
   B b("test");

   std::cout << "Before: " << b.name() << std::endl;
   a.take(std::move(b));
   std::cout << "After: " << b.name() << std::endl;

   std::cout << "A has elements: " << std::endl;
   for(B &b : a.v)
      std::cout << "  " << b.name() << std::endl;
}

2) 您正在访问一个已从中移动的值,这没有任何意义!这个答案已经很好地解释了,但下面我还包含了 std::move 的 STL 参考文本。

http://en.cppreference.com/w/cpp/utility/move

除非另有指定,否则所有具有 已从中移动,处于有效但未指定的状态。那是 只有没有前提条件的函数,例如赋值 运算符,可以在对象移动后安全地用于对象。

3)根据我的经验,我发现了两个合法的用途。在这两种情况下,仅移动对象都控制着一个物理资源,如果由两个对象共享,则会破坏所有资源。

评论

3赞 vsoftco 10/25/2015
请注意,无需显式删除 copy ctor 和 assignment 运算符。用户定义的移动语法禁用复制,例如,请参阅此处的实时示例。
0赞 Govan 10/25/2015
@tweej为什么我应该使用仅移动对象而不是唯一 ptr?
0赞 Matthieu M. 10/25/2015
@Govan:实际上,对于一个无法共享的资源来说,是一个构建砖块,你的移动对象把它包装起来,以呈现一个令人愉快的界面。如果它不能共享,你甚至如何编写一个复制构造函数?unique_ptr
0赞 tweej 10/26/2015
@Govan问得好,我也有兴趣听听其他人对此的看法。我能想到的所有原因都与性能和确定性有关,但在非性能关键代码中可能没有多大意义。这里有一个:如果类 A 中的 B 向量是 B 的unique_ptrs向量,则每次访问 B 都需要取消引用指针,并且当您遍历向量的元素时,您已经失去了硬件辅助预取的好处。我要说的是,我使用unique_ptr和shared_ptr的频率要比创建我自己的仅移动类型要多得多。
3赞 David Haim 10/24/2015 #2

关于3:当然有。我可以想到许多可以移动但不能复制的对象的例子,或者无论如何都不应该复制。

一个例子是 Socket 类:
1) 你想为“空”Socket 提供一个默认构造函数,它仍然没有收到任何 IP 或端口。
2)你想要一个获取IP和端口的构造函数。在构造时,Socket 将尝试连接,如果连接失败
,可能会抛出异常 3) 显而易见的是析构函数 - 它将断开对象的连接并释放该对象可能持有的任何底层系统资源

移动构造函数与复制构造函数怎么样?
假设我有一个函数创建一个套接字并返回它。如果我调用复制构造函数,这意味着:
新复制的套接字(返回值)将尝试连接到源套接字连接到的同一 IP 和端口。这可能根本不可能。
源套接字将被断开并销毁

尝试复制套接字很可能会造成巨大的混乱。 相反,移动构造函数很好地解决了这个问题:
返回值接收所有底层操作系统资源,而不会断开它们或破坏它们。
源套接字保持为空,析构函数没有任何东西可以断开连接或销毁。

套接字似乎很可能只会被移动,而不是被复制。

这可能适用于“Process”类 - 如果我尝试通过复制返回一个进程,我可能会尝试再次打开相同的进程,只是关闭了原始进程。一团糟!通过移动过程,我不这样做。我只是将过程从一个功能移动到另一个功能。

评论

0赞 Govan 10/25/2015
谢谢你的回答。我理解移动的需要以及移动者和复制者之间的差异。我想知道的是需要移动右值对象。您可以通过unique_ptr实现套接字。问题是 move(unique_ptr<Socket>(new Socket())) 或 move(Socket()) 之间的区别