2

我想确保 *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;  
}
4

5 回答 5

2

如果它真的operator=困扰你,你总是可以有第二个不需要演员的非模板。为避免冗余,如果 this != &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;
}
于 2011-03-09T02:31:05.363 回答
2

你想在这里做什么

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

是执行static_cast从 aA<derived*>到 a 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 和 swap实现赋值运算符。暂时保持代码简短(er)。
  2. 赋值运算符实际上并没有做任何事情,但他们会编译iff他们的“正常”,做你应该做的版本。
  3. 有两个赋值运算符;第一个分配Aggregate<T>Aggregate<T>,第二个分配Aggregate<T1>Aggregate<T2>。这直接来自托尼的回答,而且是“正确的方式”。
  4. 免费operator==的可以为未隐式定义的类型伪造比较运算符。具体来说,我们需要那些包含Aggregate<U>编译 whenU不是原始类型的代码。

练习代码

这就是所有乐趣所在:

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. 相等性测试只为我们节省了两个赋值,它们都是指针赋值(它们太便宜了,我们不需要检查)。

我想说这意味着平等检查是多余的,应该被删除。

但是,如果:

  1. T1并且T2是相同类型或存在隐式转换,并且
  2. T1的赋值运算符或T2转换运算符很昂贵(这会立即使原语脱离图片),并且
  3. bool operator== (const T1& lhs, const T2& rhs)与上述赋值/转换运算符相比,运行时成本要小得多的A

然后检查是否相等可能有意义(取决于您希望operator==返回的频率true)。

结论

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

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

于 2011-03-09T02:05:05.067 回答
1

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

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

poor_assignment& operator=(const poor_assignment& rhv)
{
    this->Release(); // == rhv.Release() in case of self-assignment
    this->Create(rhv.GetResources()); 
}
于 2011-03-09T16:20:22.580 回答
0

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

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

“脏”版本:将每个对象的地址转换为 aintptr_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;
}

编辑:实际上,第二个版本不起作用,因为编译器生成了复制赋值运算符,而是使用了它。所以只需自己实现一个。--end edit
另外,您不需要使用rhs.get(),只需使用rhs.m_t,因为您可以从类本身访问私有成员。

于 2011-03-09T02:35:33.130 回答
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;
  }
于 2011-03-09T03:26:24.920 回答