为什么叫移动赋值?
Why move-assignment is called?
我有一些 class 具有复制和移动赋值,但在我的示例中移动似乎是错误的并导致意外行为。为什么要调用 move,我怎样才能避免这种情况?
C1赋值给C2后用,但是调用了move,此时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
,则会调用正确的复制赋值运算符,但在我的实际代码中,有时会调用非赋值运算符(而是做了一个普通的副本,这也是错误的)。
希望你能给我解释一下原理。我错过了什么?
&&
在模板函数的参数上下文中与在其他情况下具有不同的含义。
它被称为 forwarding reference,它要么是右值引用,要么是非常量左值引用,具体取决于您传入的内容。
这意味着您的 template operator=
是 C1 = C2
的最佳匹配,因为两个复制分配都采用 const&
,而 C1
不是 const
。
template<typename M> CSomeClass& operator=( M&& p_rzOther )
这里,M&& p_rzOther
是一个forwarding reference。您可以将左值和右值都传递给它,包括 const
和非 const
.
在你的例子中,M
被推断为 CSomeClass &
,由于 reference collapsing 将赋值运算符转换为:
CSomeClass &operator=(CSomeClass &p_rzOther)
因为在 C2 = C1;
中,C1
不是 const
,上面的运算符比其他两个采用 const CSomeClass &
.[=29 的赋值运算符更匹配=]
你可以用 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 )
防止与其他运算符冲突。
我有一些 class 具有复制和移动赋值,但在我的示例中移动似乎是错误的并导致意外行为。为什么要调用 move,我怎样才能避免这种情况? C1赋值给C2后用,但是调用了move,此时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
,则会调用正确的复制赋值运算符,但在我的实际代码中,有时会调用非赋值运算符(而是做了一个普通的副本,这也是错误的)。
希望你能给我解释一下原理。我错过了什么?
&&
在模板函数的参数上下文中与在其他情况下具有不同的含义。
它被称为 forwarding reference,它要么是右值引用,要么是非常量左值引用,具体取决于您传入的内容。
这意味着您的 template operator=
是 C1 = C2
的最佳匹配,因为两个复制分配都采用 const&
,而 C1
不是 const
。
template<typename M> CSomeClass& operator=( M&& p_rzOther )
这里,M&& p_rzOther
是一个forwarding reference。您可以将左值和右值都传递给它,包括 const
和非 const
.
在你的例子中,M
被推断为 CSomeClass &
,由于 reference collapsing 将赋值运算符转换为:
CSomeClass &operator=(CSomeClass &p_rzOther)
因为在 C2 = C1;
中,C1
不是 const
,上面的运算符比其他两个采用 const CSomeClass &
.[=29 的赋值运算符更匹配=]
你可以用 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 )
防止与其他运算符冲突。