在为泛型类型的模板 class 重载 operator= 时检查自赋值

Checking for self-assignment when overloading operator= for template class of generic type

通常,重载和赋值运算符时,应该检查自赋值。在一个简单的非模板化 class 中,它需要以下内容:

MyClass& MyClass::operator=(const MyClass& rhs) {
    if (this != &rhs) {
        // do the assignment
    }

    return *this;
}

但是,假设 MyClass 是模板化的,并且我们想要泛化 class 可以具有的泛型类型的赋值运算符重载:

template<class T>
class MyClass
{
    template<class U>
    friend class MyClass;
    T value;

    template<class U>
    MyClass<T>& operator=(const MyClass<U>& rhs)

    //... other stuff
}

template<class T>
template<class U>
MyClass<T>& MyClass<T>::operator=(const MyClass<U>& rhs)
{
    if (this != &rhs) { //this line gives an error
        value = (T)rhs.value;
    }
}

在上述情况下,行 if (this != &rhs) 会给出编译器错误。在 MS Visual Studio 2015 中,是错误 C2446:

'==': no conversion from 'const MyClas< T > *' to 'MyClass< U > *const '

因此,在使用可以在右侧获取泛型模板类型 MyClass 实例的赋值运算符时,如何实现自赋值检查?

我建议重载 operator=

template<class T>
class MyClass
{
    template<class U>
    friend class MyClass;
    T value;

    template<class U>
    MyClass& operator=(const MyClass<U>& rhs) { ... }

    // Overload for MyClass<T>
    MyClass& operator=(const MyClass& rhs) { ... }
};

并仅在第二次重载时检查自赋值。


我想指出,如果 MyClass 的特化不简单,上述逻辑就会中断。例如,如果您使用:

template<> class MyClass<int>:public MyClass<long> { ... };

没有调用检查自分配的代码。参见 http://ideone.com/AqCsa3

我肯定很想看到

Came to MyClass<T>::operator=(const MyClass& rhs)

作为该程序的输出。

same_object 是一个接受两个引用的函数,如果它们都引用同一个对象,则 return 为真; 地址不相同,但对象相同。

template<class T, class U, class=void>
struct same_object_t {
  constexpr bool operator()(T const volatile&, U const volatile&)const{return false;}
};
template<class T>
struct same_object_t<T,T,void> {
  bool operator()(T const volatile& lhs, T const volatile& rhs)const{
    return std::addressof(lhs) == std::addressof(rhs);
  }
};
template<class T, class U>
struct same_object_t<T,U,
  typename std::enable_if<
    std::is_base_of<T, U>::value && !std::is_same<T,U>::value
  >::type
>:
  same_object_t<T,T>
{};
template<class T, class U>
struct same_object_t<T,U,
  typename std::enable_if<
    std::is_base_of<U, T>::value && !std::is_same<T,U>::value
  >::type
>:
  same_object_t<U,U>
{};
template<class T, class U>
constexpr bool same_object(T const volatile& t, U const volatile& u) {
  return same_object_t<T,U>{}(t, u);
}

template<class T>
template<class U>
MyClass<T>& MyClass<T>::operator=(const MyClass<U>& rhs)
{
  if (!same_object(*this, rhs)) {
    value = static_cast<T>(rhs.value);
  }
  return *this;
}

Live example.

由于联合和标准布局,两个不同的对象可以共享一个地址 "first member" 地址共享,以及一个数组和数组的第一个元素。来自 same_object.

的案例 return false

private/protected 继承可以打破这一点,通过多个路径从类型 T 继承的类型 U 也可以。