当重载operator = for generic type的模板类时检查自我赋值

时间:2017-06-14 17:41:50

标签: c++ c++11 templates operator-overloading assignment-operator

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

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:

  

&#39; ==&#39;:没有来自&#39; const MyClas&lt; T> *&#39;到&#39; MyClass&lt; U> * const&#39;

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

2 个答案:

答案 0 :(得分:3)

我建议重载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)

作为该计划的输出。

答案 1 :(得分:2)

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

Live example

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

private / protected继承可以打破这种情况,类型U可以通过多个路径继承。