【问题标题】:Class Assignment Operators类赋值运算符
【发布时间】:2011-05-29 10:13:51
【问题描述】:

我做了以下运算符重载测试:

#include <iostream>
#include <string>

using namespace std;

class TestClass
{
    string ClassName;

    public:

    TestClass(string Name)
    {
        ClassName = Name;
        cout << ClassName << " constructed." << endl;
    }

    ~TestClass()
    {
        cout << ClassName << " destructed." << endl;
    }

    void operator=(TestClass Other)
    {
        cout << ClassName << " in operator=" << endl;
        cout << "The address of the other class is " << &Other << "." << endl;
    }
};

int main()
{
    TestClass FirstInstance("FirstInstance");
    TestClass SecondInstance("SecondInstance");

    FirstInstance = SecondInstance;
    SecondInstance = FirstInstance;

    return 0;
}

赋值运算符的行为与预期一样,输出另一个实例的地址。

现在,我将如何真正分配来自另一个实例的东西?例如,像这样:

void operator=(TestClass Other)
{
    ClassName = Other.ClassName;
}

【问题讨论】:

  • 你也不需要,但是你有一个赋值运算符和一个析构函数,但没有复制构造函数,这看起来仍然很奇怪。根据三法则,如果您需要其中任何一个,您可能需要全部三个。
  • @sbi 当然。不过,这只是一些测试代码。
  • 不过,当我看到这一点时,反应就会开始。我还注意到,您为每个副本传递了一个 std::string 对象,而不是 const 引用。您可能想阅读this

标签: c++ class operators operator-overloading assignment-operator


【解决方案1】:

您展示的代码可以做到这一点。不过,没有人会认为这是一个特别好的实现。

这符合赋值运算符的预期:

TestClass& operator=(TestClass other)
{
    using std::swap;
    swap(ClassName, other.ClassName);
    // repeat for other member variables;
    return *this;
}

顺便说一句,您说的是“其他类”,但您只有一个类,并且该类有多个实例。

【讨论】:

  • Ben,实际上最好提供一个swap() 成员函数并调用它。不过,这比分配要好。
【解决方案2】:

赋值运算符的传统规范形式如下所示:

TestClass& operator=(const TestClass& Other);

(您也不想调用复制构造函数进行赋值)它返回对*this 的引用。

一个简单的实现会单独分配每个数据成员:

TestClass& operator=(const TestClass& Other)
{
  ClassName = Other.ClassName;
  return *this;
}

(请注意,这正是编译器生成的赋值运算符会做的事情,所以重载它是毫无用处的。不过我认为这是为了锻炼。)

更好的方法是使用 Copy-And-Swap idiom。 (如果你觉得 GMan 的回答太过分了,试试mine,它不那么详尽。:))请注意,C&S 使用复制构造函数和析构函数来进行赋值,因此需要每个副本传递对象,就像你在你的问题:

TestClass& operator=(TestClass Other)

【讨论】:

  • 我知道你知道copy-and-swap,为什么要声明参数为引用?
  • @Ben:谢谢。我添加了一条说明,使用 c&s 应该复制对象。我猜旧习惯很难改掉。 (哦,我不确定什么是“忍者编辑”,顺便说一句。)
  • 在这种情况下,它是 ninja edit,因为您按照 Ben 的建议进行了更改。
【解决方案3】:

差不多都说了,有几点说明:

  • 检查自分配,即if (&amp;other != this) // assign
  • 在这里查看operator overloading 的优秀指南

【讨论】:

  • 如果您的赋值运算符需要检查自赋值,那么可能会有更好的实现。好的实现(例如 Copy-And-Swap)不需要那种测试(这会给每个作业检查罕见情况的负担)。
  • &lt;shameless_plug&gt; 我们现在在 SO 上也有一个运算符重载常见问题解答:stackoverflow.com/questions/4421706/operator-overloading&lt;/shameless_plug&gt;
  • @sbi:感谢您的参考,我有一天会阅读它;)。我提到的对初学者来说简短而容易,只提供基本的必需品。有一天我也会阅读 C&S,但至于自检开销 - 似乎 C&S 有复制开销和在许多情况下内存分配(如果你的类包含字符串、向量等),所以它应该有一个“小心处理”标签,不是吗?
  • @davka:不过,您链接到的那个是有问题的。此外,C&S 没有开销。 I have explained why it doesn't.。简而言之:赋值是拆除旧状态,并通过从另一个对象复制数据来建立新状态。这正是复制构造函数和析构函数的作用,C&S 设法以正确的顺序使用它们以保证异常安全。
  • @davka:交换时,你分配新数据,复制新数据,交换新旧数据,释放旧数据。分配时,您取消分配旧数据,分配新数据,然后复制数据(并且您祈祷分配不会失败并抓住您的裤子)。但是交换应该是 O(1) 并且不会抛出,因此它不会影响运行时。 (例如,std::vector 交换会交换两个指针。与复制的 O(N) 和分配的 O(VeryLooong) 相比,这是可以忽略的。)
【解决方案4】:

传统上,赋值运算符和复制构造函数是通过 const 引用定义的,而不是通过值复制机制。

class TestClass 
{
public:
    //... 
    TestClass& operator=(const TestClass& Other)
    {
        m_ClassName= Other.m_ClassName;
        return *this;
    }
private:
    std::string m_ClassName;
 }

编辑:我更正了,因为我放置了没有返回 TestClass& 的代码(c.f. @sbi 的答案)

【讨论】:

  • 新的常见做法实际上确实按值传递了 RHS。它被称为copy-and-swap idiom
  • 而且只是对 RHS 的一种本能的自动排斥...(没有看一秒钟关于复制和交换习语的完整 SO 主题)...在使用多态性时按值 RHS 意味着在我的职业生涯中出现了数百个错误……我需要 几个小时 才能说服我按价值使用 RHS ;-)
【解决方案5】:

关于如何从其他类复制内容是正确的。只需使用operator= 即可分配简单的对象。

但是,请注意TestClass 包含指针成员的情况——如果您只是使用operator= 分配指针,那么两个对象都会有指向同一内存的指针,这可能不是您想要的。相反,您可能需要确保分配一些新内存并将指向的数据复制到其中,以便两个对象都有自己的数据副本。请记住,在为复制的数据分配新块之前,您还需要正确地解除分配对象已经指向的内存。

顺便说一句,你应该像这样声明你的operator=

TestClass & operator=(const TestClass & Other)
{
    ClassName = Other.ClassName;
    return *this;
}

这是重载operator= 时使用的一般约定。 return 语句允许链接赋值(如a = b = c)并通过const 引用传递参数,避免将Other 复制到函数调用中。

【讨论】:

猜你喜欢
  • 2011-11-14
  • 2019-06-30
  • 2011-08-02
  • 2019-10-06
  • 2011-11-16
  • 2013-11-30
  • 2015-10-02
  • 2015-01-27
相关资源
最近更新 更多