模板化赋值运算符问题

时间:2011-03-09 01:45:05

标签: c++ templates gcc static-cast

我想确保赋值运算符中的* this!=& rhs。但它不会编译。有什么建议吗?

template <typename T>
class A {
  public:
      A() {
          std::cout << "Default Constructor" << std::endl;
      }

      A(const T& t) : m_t(t) {
          std::cout << "Templated Constructor" << std::endl;
      }

      template <typename X>
      A( const A<X>& rhs ) : m_t( (static_cast< A<T> >(rhs)).m_t ) {
            std::cout << "Copy Constructor" << std::endl;
      }

      template <typename X>
      const A& operator=( A<X>& rhs) {
            std::cout << "Assignment Operator" << std::endl;
            if (this != static_cast< A<T>* > (&rhs) )
                m_t = rhs.get();
            return *this;
      }

      T get() { return m_t; }
  private:
      T m_t;
};


class base {};
class derived : public base {};


int main()
{
    A<base*> test1;
    A<derived*> test2;
    test1 = test2;  
}

5 个答案:

答案 0 :(得分:2)

你在这里想做什么

if (this != static_cast< A<T>* > (&rhs) )

static_castA<derived*>执行A<base*>

以下是static_cast的作用:

  

您可以显式转换指针   类型A的指针B类型的指针   如果A是B的基类,如果A不是   B的基类,编译错误   会结果。

A<base*>不是A<derived*>的基类,因此错误。

一般来说,即使A<T>可转换为A<X>X也永远不会是T的基类。因此,演员阵容不合时宜。

解决方案是使用reinterpret_cast<void*>(&rhs)代替。

更新

我更多地研究了这个;这是结果。我先给出代码,然后发表评论。

设置代码

template <typename T>
class Aggregator {
  public:
      Aggregator() {
          std::cout << "Default Constructor" << std::endl;
      }

      Aggregator(const T& t) : m_t(t) {
          std::cout << "Constructor With Argument" << std::endl;
      }

      Aggregator& operator= (const Aggregator& rhs)
      {
          std::cout << "Assignment Operator (same type)";
          if (this->get() == rhs.get()) {
              std::cout << " -- SKIPPED assignment";
          }
          else {
              T justForTestingCompilation = rhs.get();
          }
          std::cout << std::endl;
          return *this;
      }

      template <class U>
      Aggregator& operator=(const Aggregator<U>& rhs)
      {
          std::cout << "Assignment Operator (template)";
          if (this->get() == rhs.get()) {
              std::cout << " -- SKIPPED assignment";
          }
          else {
              T justForTestingCompilation = rhs.get();
          }
          std::cout << std::endl;
          return *this;
      }

      T get() const { return m_t; }
  private:
      T m_t;
};


class base {};
class derived : public base {};
class unrelated {};

// This is just for the code to compile; in practice will always return false
bool operator==(const base& lhs, const base& rhs) { return &lhs == &rhs; }

到目前为止发生的重要事项:

  1. 我没有任何复制构造函数。在现实生活中,我们将赋值逻辑移动到复制构造函数中,并使用copy and swap实现赋值运算符。暂时保持代码简短(呃)。
  2. 赋值运算符实际上并没有做任何事情,但是它们会编译iff它们的“正常”,你应该做什么样的版本。
  3. 有两个辅助操作员;第一个用于将Aggregate<T>分配给Aggregate<T>,第二个用于将Aggregate<T1>分配给Aggregate<T2>。这直接来自Tony的回答,这是“正确的方式”。
  4. 免费的operator==可以伪造一个比较运算符,用于没有隐式定义的类型。具体来说,当Aggregate<U>不是基本类型时,我们需要对包含U的代码进行编译。
  5. 练习代码

    以下是所有乐趣的地方:

    int main(int argc, char* argv[])
    {
        base b;
        derived d;
        unrelated u;
    
        Aggregator<base*> aggPB(&b);
        Aggregator<base*> aggPBDerivedInstance(&d);
        Aggregator<derived*> aggPD(&d);
        Aggregator<unrelated*> aggPU(&u);
    
        Aggregator<base> aggB(b);
        Aggregator<base> aggBDerivedInstance(d); // slicing occurs here
        Aggregator<derived> aggD(d);
        Aggregator<unrelated> aggU(u);
    
        std::cout << "1:" << std::endl;
    
        // base* = base*; should compile, but SKIP assignment
        // Reason: aggregate values are the same pointer
        aggPB = aggPB;
    
        // base = base; should compile, perform assignment
        // Reason: aggregate values are different copies of same object
        aggB = aggB;
    
        std::cout << "2:" << std::endl;
    
        // base* = base*; should compile, perform assignment
        // Reason: aggregate values are pointers to different objects
        aggPB = aggPBDerivedInstance;
    
        // base = base; should compile, perform assignment
        // Reason: aggregate values are (copies of) different objects
        aggB = aggBDerivedInstance;
    
        std::cout << "3:" << std::endl;
    
        // base* = derived*; should compile, perform assignment
        // Reason: aggregate values are pointers to different objects
        aggPB = aggPD;
    
        // base = derived; should compile, perform assignment (SLICING!)
        // Reason: derived is implicitly convertible to base, aggregates are (copies of) different objects
        aggB = aggD;
    
        std::cout << "4:" << std::endl;
    
        // base* = derived*; should compile, but SKIP assignment
        // Reason: aggregate values are (differently typed) pointers to same object
        aggPBDerivedInstance = aggPD;
    
        // base = derived; should compile, perform assignment (SLICING!)
        // Reason: derived is implicitly convertible to base, aggregates are (copies of) different objects
        aggBDerivedInstance = aggD;
    
        std::cout << "5:" << std::endl;
    
        // derived* = base*; should NOT compile
        // Reason: base* not implicitly convertible to derived*
        // aggPD = aggPB;
    
        // derived = base; should NOT compile
        // Reason: base not implicitly convertible to derived
        // aggD = aggB;
    
        return 0;
    }
    

    这将输出:

    Constructor With Argument
    Constructor With Argument
    Constructor With Argument
    Constructor With Argument
    Constructor With Argument
    Constructor With Argument
    Constructor With Argument
    Constructor With Argument
    1:
    Assignment Operator (same type) -- SKIPPED assignment
    Assignment Operator (same type)
    2:
    Assignment Operator (same type)
    Assignment Operator (same type)
    3:
    Assignment Operator (template)
    Assignment Operator (template)
    4:
    Assignment Operator (template) -- SKIPPED assignment
    Assignment Operator (template)
    5:
    

    那么......我们从中学到了什么?

    1. 除非在聚合类型之间存在隐式转换,否则模板化的赋值运算符将无法编译。这是好事。这段代码无法编译而不是崩溃。
    2. 对等式的测试只保存了两个赋值,它们都是指针赋值(它们很便宜,我们不需要检查)。
    3. 我要说这意味着相等检查是多余的,应该删除。

      但是,如果:

      1. T1T2属于同一类型或存在隐式转化,
      2. T1的赋值运算符或T2的转换运算符很昂贵(这会立即将原语从图片中删除),
      3. 运行成本比上述分配/转换运算符小得多的bool operator== (const T1& lhs, const T2& rhs)
      4. 然后检查相等可能有意义(取决于您期望operator==返回true的频率。)

        结论

        如果你打算只使用Aggregator<T>指针类型(或任何其他原语),那么相等测试是多余的。

        如果您打算使用昂贵的构造类类型,那么您将需要一个有意义的相等运算符来使用它们。如果您也将它用于不同类型,则还需要转换运算符。

答案 1 :(得分:2)

如果真的困扰你,你总是可以拥有第二个不需要演员的非模板operator=。为了避免重复,如果这个!=&amp; rhs它可以显式调用模板版本。说明如何调用右操作符的示例:

#include <iostream>

template <class T>
struct X
{
    X& operator=(X& rhs)
    {
        std::cout << "non-template " << (this == &rhs ? "self\n" : "other\n");
    }

    template <class U>
    X& operator=(X<U>& rhs)
    {
        std::cout << "template\n";
    }
};

int main()
{
    X<int> x;
    x = x;
    X<int> y;
    x = y;
    X<double> z;
    x = z;
}

答案 2 :(得分:1)

在您的情况下,无需测试自我分配。与一些教程可能提出的建议相反,自我指派测试对于重载赋值运算符一般不是必不可少的。

只有当(实现不当)赋值运算符首先释放资源,然后创建新资源作为右侧操作数的资源副本时,才需要这样做。只有这样,自我分配才会变成灾难性的,因为左手对象会同时释放右手操作数(本身)的资源。

poor_assignment& operator=(const poor_assignment& rhv)
{
    this->Release(); // == rhv.Release() in case of self-assignment
    this->Create(rhv.GetResources()); 
}

答案 3 :(得分:0)

好的版本:实现与类本身具有完全相同类型的复制赋值运算符:

const A& operator=( A<T>& rhs) {
    std::cout << "copy assignment operator" << std::endl;
    if(this != &rhs)
        m_t = rhs.m_t;
    return *this;
}

'dirty'版本:将每个对象的地址转换为intptr_t并比较普通值:

template<class X>
const A& operator=( A<X>& rhs) {
    std::cout << "Assignment Operator" << std::endl;
    if((intptr_t)(this) != (intptr_t)(&rhs))
        m_t = rhs.get();
    return *this;
}

编辑:实际上,第二个版本不起作用,因为编译器生成的复制赋值运算符代替使用。所以你自己实施一个。 - 结束编辑
此外,您不需要使用rhs.get(),只需使用rhs.m_t,因为您可以从类本身访问私有成员。

答案 4 :(得分:0)

我个人认为以下是最优雅的解决方案。我不知道为什么我没有得到它 - 我最初使用流血的c ++编译器,它似乎失败了 - 但g ++这是最干净的?

如果有人不同意我,我会删除我的答案并将其交给其他人。请注意,A *实际上是指A *,但需要注意

  template <typename X>
  const A& operator=( A<X>& rhs) {
        std::cout << "Assignment Operator" << std::endl;
        if (this != reinterpret_cast< A* >(&rhs))  
            m_t = rhs.get();               
        return *this;
  }