为什么会被称为"move-assignment"?

3

我有一些带有复制和移动分配的类,但是在我的示例中看起来移动操作是错误的,并导致了意外行为。为什么会调用移动函数并且我该如何避免这种情况?C1被赋值给C2后仍然需要使用,但是移动函数被调用并且C1变为空。

#include <iostream>

class CSomeClass
{
protected:
   size_t m_uiSize = 0u;

public:
   CSomeClass() {}

   ~CSomeClass() {}

   size_t size() const { return m_uiSize; }

   void resize( size_t p_uiNewSize ) { m_uiSize = p_uiNewSize; }

   /* This operator I was expected to be called in all cases. */
   CSomeClass& operator=( const CSomeClass& p_rzOther )
   {
      std::wcout << "Copy explicit" << std::endl;
      m_uiSize = p_rzOther.size();
      return *this;
   }

   CSomeClass& operator=( CSomeClass&& p_rzOther )
   {
      std::wcout << "Move explicit" << std::endl;
      m_uiSize = p_rzOther.size();
      p_rzOther.resize( 0u );
      return *this;
   }

#if 1
   template<typename M> CSomeClass& operator=( const M& p_rzOther )
   {
      std::wcout << "Copy UNDEF" << std::endl;
      m_uiSize = p_rzOther.size();
      return *this;
   }

   template<typename M> CSomeClass& operator=( M&& p_rzOther )
   {
      std::wcout << "Move UNDEF" << std::endl;
      p_rzOther.resize( 0u );
      return *this;
   }
#endif
};


int main()
{
   CSomeClass C1;
   CSomeClass C2;

   C1.resize( 1u );

   std::wcout << L"C1 size before: " << C2.size() << std::endl;

   C2 = C1;

   std::wcout << L"C1 size after: " << C2.size() << std::endl;

   return 0;
}

这会导致以下输出结果:
C1 size before: 1
Move UNDEF
C1 size after: 0

我的实际问题比较复杂(有更多的模板和大量的变量分配方式)。

如果把#if 1 改成 #if 0,正确的复制赋值运算符将被调用。但在我的实际代码中,有些情况下没有调用任何分配运算符(而是进行了错误的简单复制)。

希望您可以向我解释这种机制。我错过了什么吗?


3
你不小心做了一个转发引用。 - HolyBlackCat
@HolyBlackCat:谢谢,我不熟悉转发引用,也许我应该去了解一下。目前,我不明白为什么这是错误的。 - Anton F.
2个回答

3

&&在模板函数的参数上下文中具有不同的含义,与其他情况不同。

这被称为转发引用,它将是一个右值引用或非const左值引用,取决于您传递的内容。

这意味着您的template operator=是最适合C1 = C2的匹配,因为两个复制赋值都采用const&,而C1不是const


3
template<typename M> CSomeClass& operator=( M&& p_rzOther )

在这里,M&& p_rzOther 是一个转发引用。可以向它传递左值和右值,包括const 和非const
在您的情况下,M 被推断为 CSomeClass &,由于引用折叠,将赋值运算符转换为:
CSomeClass &operator=(CSomeClass &p_rzOther)

由于在C2 = C1;中,C1不是const,因此上述运算符比其他两个采用const CSomeClass&的赋值运算符更好。

您可以通过SFINAE来解决这个问题,通过防止M成为CSomeClass(可能带有cv限定符,可能是对其引用):

template <
    typename M,
    std::enable_if_t<
        !std::is_same_v<
            CSomeClass,
            std::remove_cv_t<std::remove_reference_t<M>>
        >,
        decltype(nullptr)
    > = nullptr
>
CSomeClass &operator=(M &&p_rzOther)

由于这个operator=可以处理有和没有const的值类型,所以您不需要另一个。我建议删除。

template<typename M> CSomeClass& operator=( const M& p_rzOther )

为了防止它与其他运算符发生冲突。


谢谢解释。现在我明白了基础知识。我知道我需要如何更改我的代码。我将放弃未使用的转发并用其他方法替换它们。 - Anton F.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接