C ++赋值运算符 - 生成的编译器还是自定义?

时间:2010-08-26 04:08:15

标签: c++

我有一个中等复杂的C ++类,它包含从光盘读取的一组数据。它包含浮动,整体和结构的折衷混合,现在通常使用。在主要代码审查期间,询问我们是否有自定义赋值运算符,或者我们依赖于编译器生成的版本,如果是,我们如何知道它是否正常工作?好吧,我们没有编写自定义任务,因此添加了一个单元测试来检查我们是否这样做:

CalibDataSet datasetA = getDataSet();
CalibDataSet dataSetB = datasetA;

然后datasetB与datasetA相同。几百行左右。现在,客户坚持认为我们不能依赖编译器(gcc)对未来的版本是正确的,我们应该编写自己的版本。他们坚持这个是正确的吗?

其他信息:

我对已发布的答案/评论和响应时间印象深刻。另一种提出这个问题的方法可能是: POD结构/类什么时候变成'不'POD结构/类?

5 个答案:

答案 0 :(得分:13)

众所周知,自动生成的赋值运算符将执行什么操作 - 它被定义为标准的一部分,符合标准的C ++编译器将始终生成正确行为的赋值运算符(如果没有,那么它不会是符合标准的编译器。)

如果您编写了自己的析构函数或复制构造函数,通常只需要编写自己的赋值运算符。如果您不需要那些,那么您也不需要赋值运算符。

答案 1 :(得分:8)

显式定义赋值运算符的最常见原因是支持“远程所有权” - 基本上是一个包含一个或多个指针的类,并且拥有这些指针所引用的资源。在这种情况下,您通常需要定义赋值,复制(即复制构造函数)和销毁。这种情况有三种主要策略(按使用频率降低排序):

  1. 深层复制
  2. 引用计数
  3. 转让所有权
  4. 深层复制意味着为分配/复制的目标分配新资源。例如,字符串类具有指向字符串内容的指针;分配时,分配会分配一个新缓冲区来保存目标中的新内容,并将数据从源复制到目标缓冲区。这用于许多标准类的大多数当前实现,例如std::stringstd::vector

    引用计数使用也很常见。 std::string的许多(大多数?)旧实现使用了引用计数。在这种情况下,不是为字符串分配和复制数据,而是简单地增加引用计数以指示引用特定数据缓冲区的字符串对象的数量。当/如果字符串的内容被修改时,您只分配了一个新的缓冲区,因此它需要与其他内容不同(即,它使用写入时的副本)。但是,对于多线程,您需要同步对引用计数的访问,这通常会对性能产生严重影响,因此在较新的代码中这是相当不寻常的(主要用于存储所以很多数据的时候)值得浪费一点CPU时间来避免这样的副本)。

    所有权转让相对不寻常。这是std::auto_ptr所做的。当您分配或复制某些内容时,分配/复制的来源基本上被销毁 - 数据从一个传输到另一个。这是(或可能)有用,但语义与正常分配完全不同,因为它通常违反直觉。同时,在适当的情况下,它可以提供高效率和简单性。 C ++ 0x将通过添加unique_ptr类型使其更加明确,并且还添加rvalue引用,使得所有权的转移更易于管理,这使得在一个相当大的类别的情况下实现所有权的转移变得容易它可以提高的性能而不会导致明显违反直觉的语义。

    回到最初的问题,但是,如果你没有远程所有权开始 - 也就是说,你的类不包含任何指针,你很可能不应该明确定义赋值运算符(或者复制或复制ctor)。阻止隐式定义的赋值运算符起作用的编译器错误会阻止传递任何大量的回归测试。

    即使它以某种方式被释放,你对它的防御也就是不使用它。没有真正的空间可以解决这样一个版本将在几个小时内被替换的问题。对于中型到大型的现有项目,您不希望切换到编译器,直到它在任何情况下被广泛使用一段时间。

答案 2 :(得分:2)

如果编译器没有正确生成赋值,那么除了实现赋值重载(比如编译器损坏的事实)之外,还有更大的问题需要担心。除非你的类包含指针,否则没有必要提供你自己的重载;但是,请求显式重载是合理的,不是因为编译器可能会中断(这是荒谬的),而是记录您的意图,即允许赋值并以这种方式运行。在C ++ 0x中,通过对编译器生成的版本使用= default,可以记录意图并节省时间。

答案 3 :(得分:1)

如果你的班级中有一些你应该管理的资源,那么编写你自己的赋值运算符是有意义的,否则依赖于编译器生成的。

答案 4 :(得分:0)

我想如果你正在处理字符串,可能会出现浅拷贝深拷贝的问题。 http://www.learncpp.com/cpp-tutorial/912-shallow-vs-deep-copying/

但我认为始终建议为用户定义的类编写赋值重载。