【问题标题】:Checking for self-assignment when overloading operator= for template class of generic type为泛型类型的模板类重载 operator= 时检查自赋值
【发布时间】:2017-11-16 23:51:48
【问题描述】:

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

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 != &amp;rhs) 行将给出编译器错误。在 MS Visual Studio 2015 中,是错误 C2446:

'==':没有从 'const MyClas *' 转换为 'MyClass *const '

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

【问题讨论】:

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


    【解决方案1】:

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

    作为该程序的输出。

    【讨论】:

    • 当唯一的区别是检查自分配时,它会不会导致不必要的重复代码?或者,如果我遵循您的建议并另外实现一个私有函数,该函数实际执行分配并由operator= 的两个版本调用,则过于复杂?
    • template&lt;&gt; class MyClass&lt;int&gt;:public MyClass&lt;long&gt; ;)
    • @Juddy,如果有两个或更少的成员,我不会担心重复的代码。否则,拥有包含公共代码的private 成员函数是有意义的。
    • @Yakk,这对我来说很神秘。能详细点吗?
    • @RSahu 如果MyClass&lt;int&gt; 也是MyClass&lt;long&gt; 你的代码会发生什么?老实说,我不知道,但似乎有什么东西会坏掉。
    【解决方案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.

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多