7

I want to know if there is a safe programming practice that would alert a coder to this subtle behavior when it takes place or, even better, avoid it in the first place.

A user of struct A might not realize there is no move constructor. In their attempt to call the absent ctor they get neither a compiler warning or any run-time indication that a copy ctor was called instead.

An answer below explains the conversion that takes place but I don't see any rationale for this being a good thing. If the constructor taking a const reference as a parameter were missing there would be a compile time error rather than just resolving to the non-const reference version. So why wouldn't an attempt at using move semantics result in a compile time error when there is no move semantics implemented in the class?

Is there a way to avoid this behavior with some compile time option or at least a way to detect it during run time?

One could assert(source is null) after the move if they were anticipating the problem but that is true of so many problems.

Example, when:

struct A {
    A() {...}
    A(A &a) {...}
    A(A const & A) {...}
};

is constructed as in:

A a1;
A a2 = std::move(a1);  //calls const copy (but how would I know?)

this results in the const version of the copy ctor being called. Now two objects might have a pointer to a single resource while one of them is likely to be calling it's destructor soon.

4

4 回答 4

4

由于std::move返回一个右值,它可以转换为一个 const ref,这就是为什么复制 ctor 被静默调用的原因。您可以通过多种方式解决此问题。

  1. 简单的方法,如果你的类没有动态分配,只需像这样使用默认的 mctor。
    A(A &&) = default;
  2. 摆脱 cctor 中的 const,prolly 不是一个好主意,但它不会编译

  3. 只需实现你自己的移动构造函数,如果它没有 mctor,你为什么要移动到一个对象


这有点离题,但您也可以将成员函数限定为仅使用这样的可修改左值。

int func()&;

如果您正在处理遗留代码,这里是编译时检查可移动构造

于 2013-08-16T19:01:06.023 回答
3

没有一种安全的编程实践会提醒这种事情,因为在之后进行复制std::move非常普遍和正常的。添加警告将导致几乎所有内容都<algorithm>开始触发警告,我们需要一个全新的函数,我们将使用它的工作方式与现在完全一样std::move。如果没有移动构造函数,很多算法都依赖于复制,否则就依赖于移动,std::move就是. 充其量你应该争论一个std::force_move.

其次,完全没有必要。采用您展示的代码的修改版本。

void legacy_api(A a1) {
   A a2 = std::move(a1);
   ...

你说它巧妙地使用了昂贵的副本而不是微妙的举动是一个问题。我不同意,它需要的是对象的一个​​新实例,可能会破坏以前的实例。如果代码需要另一个实例,那么它需要另一个实例。它是否破坏了先前的应该完全无关紧要。如果代码在不需要另一个实例的情况下正在移动,那么很明显,遗留 API 是混合编写的,并且上述警告无济于事。我想不出一个函数需要移动但不需要复制的任何原因,这样的事情没有任何目的。

最后,“现在两个对象可能有一个指向单个资源的指针,而其中一个可能很快就会调用它的析构函数。” 不,如果发生这种情况,则意味着A' 的复制构造函数有一个错误,句号。修复代码中的错误,问题就会消失。像魔法一样!

于 2013-08-16T21:14:09.807 回答
1

因为 std::move 返回对右值的引用(如在 A&& 中),它不能转换为对左值(A&)的引用,但可以转换为对左值的 const 引用(const A&)。

int x = 5。这里 5 是一个右值,永远不能绑定到一个左值,在您的示例中也是如此,因为您使用了 std::move(a1)

于 2013-08-16T00:53:54.023 回答
1

如果您是要移动的类的所有者,但该类没有移动构造函数,因为您不想移动它,则可以编写:

class A
{
public:
   // Other constuctors

   A(A&&) = delete;
};

因此,当您尝试 move 时A,如果我没有记错,则会引发编译器错误。如果您希望对象始终被移动,您当然应该编写自己的移动构造函数,或者启用默认的移动构造函数:

 A(A&&) = default;

如果您不是该类的所有者,我认为没有直接的方法可以避免调用复制构造函数。也许一个可能的成语强迫它如下:

template<bool b, typename T>
using enabling = typename std::enable_if<b, T>::type;

template<typename T>
constexpr bool movable()
{
   return std::is_move_constructible<T>::value;
}

template<typename T>
enabling<movable<T>(), T&&> movify(T&& t)
{
    return std::move(t);
}

A a1;
A a2 = movify(a1);

那应该行得通。我没有测试过,但我觉得你已经抓住了这个想法。

于 2013-08-16T22:56:18.900 回答