模板化赋值运算符问题

发布于 2024-10-21 05:29:23 字数 1036 浏览 2 评论 0原文

我想确保赋值运算符中的 *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;  
}

I want to make sure that *this != &rhs in the assignment operator. But it won't compile. Any suggestions?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

春夜浅 2024-10-28 05:29:23

如果它确实让您烦恼,您总是可以拥有第二个不需要强制转换的非模板化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;
}

If it really bothers you, you can always have a second non-templated operator= that doesn't need the cast. To avoid redudancy, if this != &rhs it can explicitly invoke the template version. Example illustrating how the right operator gets called:

#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;
}
孤檠 2024-10-28 05:29:23

您在这里尝试执行的操作

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

是执行从 A<衍生*>Astatic_cast

这是 static_cast 的作用:

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

A 不是 A 的基类,因此会出现错误。

一般来说,显然 A 永远不会是 A 的基类,即使 X 可转换为 T。所以这个演员阵容就不在考虑之列了。

解决方案是使用 reinterpret_cast(&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. 我没有任何复制构造函数。在现实生活中,我们会将赋值逻辑移至复制构造函数中,并通过复制和交换实现赋值运算符。现在保持代码简短。
  2. 赋值运算符实际上不执行任何操作,但它们会编译iff它们的“正常”版本,即“你应该做什么”的版本。
  3. 有两个赋值运算符;第一个用于将 Aggregate 分配给 Aggregate,第二个用于将 Aggregate 分配给 Aggregate< T2>。这直接来自托尼的回答,这是“正确的方式”。
  4. 免费的operator==可以为未隐式定义的类型伪造一个比较运算符。具体来说,当 U 不是基本类型时,我们需要包含 Aggregate 的代码进行编译。

练习代码

这就是所有乐趣所在:

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. T1T2 是相同类型或存在隐式转换,
  2. T1 的赋值运算符或 T2 的转换运算符非常昂贵(这会立即将原语从图片中删除),
  3. bool 运算符== (const T1& lhs, const T2& rhs) 的运行时间成本比上述赋值/转换运算符小得多

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

结论

如果您打算仅将 Aggregator 与指针类型(或任何其他原语)一起使用,那么相等性测试是多余的。

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

What you are trying to do here

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

is perform a static_cast from a A<derived*> to a A<base*>.

Here's what static_cast does:

You can explicitly convert a pointer
of a type A to a pointer of a type B
if A is a base class of B. If A is not
a base class of B, a compiler error
will result.

A<base*> is not a base class of A<derived*>, hence the error.

In general, obviously A<T> will never be a base class of A<X>, even if X is convertible to T. So that cast is out of the equation.

A solution would be to use reinterpret_cast<void*>(&rhs) instead.

Update

I worked on this some more; here are the results. I 'll give the code first, then comment.

Setup code

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

The important points on what is going on so far:

  1. I don't have any copy constructor. In real life, we 'd move the assignment logic into the copy constructor and implement the assignment operator with copy and swap. Keeping the code short(er) for now.
  2. The assignment operators don't actually do anything, but they will compile iff their "normal", do-what-you-should version would.
  3. There are two assingment operators; the first one for assigning Aggregate<T> to Aggregate<T> and the second for assigning Aggregate<T1> to Aggregate<T2>. This is straight from Tony's answer, and it's "the right way".
  4. The free operator== is there to fake a comparison operator for types where one is not implicitly defined. Specifically, we need that for code that contains Aggregate<U> to compile when U is not a primitive type.

Excercise code

Here's where all the fun is:

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

This will output:

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:

So... what do we learn from this?

  1. The templated assignment operator, as written, will not compile unless there's an implicit conversion between the types aggregated. That's a good thing. This code will fail to compile rather than crash on you.
  2. The test for equality only saved us two assignments, and both of them are pointer assignments (which are so cheap we need not have checked).

I 'd say this means that the equality check is superfluous and should be removed.

However, if:

  1. T1 and T2 are the same type or an implicit conversion exists, and
  2. T1's assignment operator or T2's conversion operator is expensive (this immediately takes primitives out of the picture), and
  3. A bool operator== (const T1& lhs, const T2& rhs) that has a much smaller runtime cost than the above assignment/conversion operators

then checking for equality might make sense (depends on how often you would expect operator== to return true).

Conclusion

If you intend to use Aggregator<T> just with pointer types (or any other primitive), then the equality tests are superfluous.

If you intend to use it with expensive to construct class types, then you 'll need a meaningful equality operator to go with them. If you use it with different types as well, conversion operators will also be required.

初懵 2024-10-28 05:29:23

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

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

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

There is no need to test for self-assignment in your case. Self-assignment tests, contrary to what some tutorials may suggest, are not essential to overloaded assignment operators in general.

That is only needed if the (poorly implemented) assignment operator first releases the resources and then creates new resources to be the copy of the right-hand operarand's resources. Only then would self-assignment turn out to be catastrophic, because the left-hand object would have released the resources of the right-hand operand (itself) at the same time.

poor_assignment& operator=(const poor_assignment& rhv)
{
    this->Release(); // == rhv.Release() in case of self-assignment
    this->Create(rhv.GetResources()); 
}
依 靠 2024-10-28 05:29:23

我个人认为以下是最优雅的解决方案。我不知道为什么我一开始没有得到这一点 - 我最初使用的是 Bloodshed 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;
  }

I personally think the following is the most elegant solution. I am not sure why I didn't get that in the first place - I initially was using the bloodshed c++ compiler and it seemed to fail - but g++ this is cleanest?

If people disagree with me, I'll remove my answer and give it to someone else. Note that the A* actually means A* but is note required

  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;
  }
好多鱼好多余 2024-10-28 05:29:23

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

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

“脏”版本:将每个对象的地址转换为 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,因为您可以从类本身访问私有成员。

The good version: Implement the copy assignment operator that takes exactly the same type as the class itself:

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

The 'dirty' version: Cast the address of each object to a intptr_t and compare the plain values:

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

Edit: Actually, this second version won't work, because of the compiler generated copy assignment operator that is used instead. So just implement one yourself. --end edit
Also, you don't need to use rhs.get(), just use rhs.m_t, as you can access private members from the class itself.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文