在 C++11 中定义只移动对象是否有意义?
Is there a point to define move-only objects in c++11?
我之前有一个关于使用 unique-ptrs 的问题。我收到 建议使用只能移动的对象。我定义了一个 class 如下:
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();
我的问题:
- 即使我默认了移动构造函数,我也无法编写 a.take(b) 并且出现编译错误。我知道复制构造函数已被删除,但似乎合乎逻辑的选择是在默认情况下使用移动构造函数而无需像这样编写 std::move a.take(b)
- 在结果中 "test" 被打印了两次。为什么在调用 move 后 b 对象没有被删除? 如果 b 对象仍然存在并且它的副本已发送到 a.take(move(b)) 那么这意味着我们没有'对右值对象没有任何使用。
- 像上面那样使用只移动对象是个好习惯吗(删除复制构造函数和赋值运算符并默认移动构造函数和移动赋值)?
是的,有一点。第一个想到的例子是管理 cannot/should 不在对象之间共享的资源(可能是物理资源)的对象。
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) 您正在访问一个已被移出的值,这没有任何意义! This answer 已经做了很好的解释,但下面我还包含了 std::move.
的 STL 参考文本
http://en.cppreference.com/w/cpp/utility/move
Unless otherwise specified, all standard library objects that have
been moved from are placed in a valid but unspecified state. That is,
only the functions without preconditions, such as the assignment
operator, can be safely used on the object after it was moved from.
3) 根据我的经验,我发现了两种合法用途。在这两种情况下,只能移动的对象控制着一个物理资源,如果由两个对象共享,这将破坏一切。
约3:当然有。我可以想到许多可以移动但不能复制的对象的例子,或者无论如何都不应该。
一个例子是套接字class:
1) 你想为 "empty" 套接字提供一个默认构造函数,它仍然没有收到任何 IP 或端口。
2)你想给一个构造函数来获取IP和端口。在构建时,Socket 将尝试连接,如果连接失败可能会抛出异常
3) 显而易见的是析构函数——它将断开对象并释放该对象可能持有的任何底层系统资源
移动构造函数与复制构造函数呢?
假设我有一个创建套接字并 return 的函数。如果我调用复制构造函数,这意味着:
新复制的套接字(return 值)将尝试连接到与源套接字连接到的相同 IP 和端口。这可能根本不可能。
源套接字将被断开并销毁
尝试复制套接字很可能只会造成一团糟。
相反,移动构造函数很好地解决了这个问题:
return 值接收所有底层 OS 资源而不断开它们或销毁它们。
源套接字保持为空,析构函数没有要断开或销毁的东西。
看来套接字最有可能只是移动,而不是复制。
这可能适用于 "Process" class - 如果我尝试通过复制 return 一个进程,我可能会尝试再次打开相同的进程,只是关闭原来的。一团糟!通过移动周围的过程我不做。我只是将过程从一个函数移动到另一个函数。
我之前有一个关于使用 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();
我的问题:
- 即使我默认了移动构造函数,我也无法编写 a.take(b) 并且出现编译错误。我知道复制构造函数已被删除,但似乎合乎逻辑的选择是在默认情况下使用移动构造函数而无需像这样编写 std::move a.take(b)
- 在结果中 "test" 被打印了两次。为什么在调用 move 后 b 对象没有被删除? 如果 b 对象仍然存在并且它的副本已发送到 a.take(move(b)) 那么这意味着我们没有'对右值对象没有任何使用。
- 像上面那样使用只移动对象是个好习惯吗(删除复制构造函数和赋值运算符并默认移动构造函数和移动赋值)?
是的,有一点。第一个想到的例子是管理 cannot/should 不在对象之间共享的资源(可能是物理资源)的对象。
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) 您正在访问一个已被移出的值,这没有任何意义! This answer 已经做了很好的解释,但下面我还包含了 std::move.
的 STL 参考文本http://en.cppreference.com/w/cpp/utility/move
Unless otherwise specified, all standard library objects that have been moved from are placed in a valid but unspecified state. That is, only the functions without preconditions, such as the assignment operator, can be safely used on the object after it was moved from.
3) 根据我的经验,我发现了两种合法用途。在这两种情况下,只能移动的对象控制着一个物理资源,如果由两个对象共享,这将破坏一切。
约3:当然有。我可以想到许多可以移动但不能复制的对象的例子,或者无论如何都不应该。
一个例子是套接字class:
1) 你想为 "empty" 套接字提供一个默认构造函数,它仍然没有收到任何 IP 或端口。
2)你想给一个构造函数来获取IP和端口。在构建时,Socket 将尝试连接,如果连接失败可能会抛出异常
3) 显而易见的是析构函数——它将断开对象并释放该对象可能持有的任何底层系统资源
移动构造函数与复制构造函数呢?
假设我有一个创建套接字并 return 的函数。如果我调用复制构造函数,这意味着:
新复制的套接字(return 值)将尝试连接到与源套接字连接到的相同 IP 和端口。这可能根本不可能。
源套接字将被断开并销毁
尝试复制套接字很可能只会造成一团糟。
相反,移动构造函数很好地解决了这个问题:
return 值接收所有底层 OS 资源而不断开它们或销毁它们。
源套接字保持为空,析构函数没有要断开或销毁的东西。
看来套接字最有可能只是移动,而不是复制。
这可能适用于 "Process" class - 如果我尝试通过复制 return 一个进程,我可能会尝试再次打开相同的进程,只是关闭原来的。一团糟!通过移动周围的过程我不做。我只是将过程从一个函数移动到另一个函数。