重载泛型类型的模板类时检查运算符=时的自赋值

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

本文关键字:运算符 赋值 检查 泛型类型 重载      更新时间:2023-10-16

通常,当重载和分配运算符时,应该检查自赋值。在一个简单的非模板化类中,它如下所示:

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

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

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:

'

==':没有从 'const MyClas *' 到 'MyClass *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是一个

接受两个引用的函数,如果它们都引用同一个对象,则返回true;不是同一个地址,而是同一个对象。

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;
}

活生生的例子。

由于联合和标准布局"第一成员"地址共享,两个不同的对象可以共享一个地址,以及数组和数组的第一个元素。 这些案例从same_object返回false

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