根据 operator= 实现复制构造函数
Posted
技术标签:
【中文标题】根据 operator= 实现复制构造函数【英文标题】:Implementing the copy constructor in terms of operator= 【发布时间】:2011-04-08 19:15:33 【问题描述】:如果operator=
被正确定义,是否可以使用以下作为复制构造函数?
MyClass::MyClass(MyClass const &_copy)
*this = _copy;
【问题讨论】:
使用copy-and-swap idiom。 通常,复制赋值操作符会做一些清理工作。如果您的类有一个指向动态分配内存的指针,则复制赋值运算符应该做的第一件事就是释放该内存。复制构造函数的这种实现会给复制赋值运算符一个悬空指针,你不想删除它。 即使您使用智能指针(在这种情况下删除不会成为问题),您仍然会毫无意义地默认构造和分配所有成员变量。只需使用复制和交换。 【参考方案1】:如果MyClass
的所有成员都有默认构造函数,是的。
请注意,通常情况相反:
class MyClass
public:
MyClass(MyClass const&); // Implemented
void swap(MyClass&) throw(); // Implemented
MyClass& operator=(MyClass rhs) rhs.swap(*this); return *this;
;
我们通过operator=
中的值传递,以便调用复制构造函数。请注意,一切都是异常安全的,因为swap
保证不会抛出(您必须在实现中确保这一点)。
按要求编辑按值调用的内容:operator=
可以写成
MyClass& MyClass::operator=(MyClass const& rhs)
MyClass tmp(rhs);
tmp.swap(*this);
return *this;
C++ 学生通常被告知通过引用传递类实例,因为如果它们是按值传递的,就会调用复制构造函数。在我们的例子中,无论如何我们都必须复制rhs
,所以按值传递就可以了。
因此,operator=
(第一个版本,按值调用)为:
rhs
(通过复制构造函数,自动调用)
用*this
交换其内容
返回*this
并让rhs
(包含旧值)在方法退出时被销毁。
现在,我们有了这个按值调用的额外奖励。如果传递给operator=
的对象(或任何通过值获取其参数的函数)是临时对象,编译器可以(并且通常确实)根本不进行任何复制。这称为复制省略。
因此,如果rhs
是临时的,则不会进行复制。我们只剩下:
this
和rhs
的内容
摧毁rhs
因此,在这种情况下,按值传递比按引用传递更有效。
【讨论】:
其实MyClass有没有默认的构造函数没关系。只有当数据成员和基类有一个... 好的,谢谢。我这样做是为了避免在operator=
和复制构造函数的实现中出现代码重复。使用复制和交换习语,代码在复制构造函数和swap
方法中重复。我说的对吗?
@gregseth:不完全是。交换操作通常进行“浅”交换,例如,仅交换指针(适用时)。复制语义通常是“深”的,因此与交换语义完全不同。您没有复制 ctor/operator= 通常出现的代码重复,因为 operator= 根据复制 ctor 实现。
移动 ctor 和分配如何适应这个?
@NicHartley:很好。首先,以最有效的方式编写您的移动 ctor。如果是noexcept
(即它永远不会抛出),那么你可以使用std::swap
,而不是自己实现swap
。如果不是noexcept
,则需要认真考虑异常安全(这很难)。赋值运算符保持原样,按值取值并交换(现在使用std::swap
)。如果现在您想要移动语义但没有复制语义,那么只需让赋值运算符采用右值引用而不是按值引用,然后像往常一样交换。【参考方案2】:
根据异常安全的复制构造函数来实现 operator= 更为可取。请参阅 Herb Sutter 的示例 4,了解该技术的解释以及为什么它是一个好主意。
http://www.gotw.ca/gotw/059.htm
【讨论】:
【参考方案3】:这个实现意味着所有数据成员(和基类)的默认构造函数都可以从 MyClass 获得和访问,因为在进行赋值之前,它们会被首先调用。即使在这种情况下,对构造函数进行这种额外调用也可能代价高昂(取决于类的内容)。
我仍然会坚持通过初始化列表单独实现复制构造函数,即使这意味着要编写更多代码。
另一件事:这个实现可能有副作用(例如,如果你有动态分配的成员)。
【讨论】:
【参考方案4】:虽然最终结果相同,但成员首先默认初始化,之后才复制。
对于“昂贵”的成员,您最好使用初始化列表进行复制构造。
struct C
ExpensiveType member;
C( const C& other ): member(other.member)
;
;
【讨论】:
不能初始化外部构造。 @GMan:该死。我的意思是写复制构造函数,而不是赋值。很抱歉。【参考方案5】:如果MyClass
分配内存或者是可变的,我会说这是不行的。
【讨论】:
如果它不是可变的,那么它就没有operator=
——这是一个变异函数。或者我所说的 mutable 和你的意思不是一样的吗?【参考方案6】:
是的。
就个人而言,如果您的类没有指针,尽管我不会重载等于运算符或编写复制构造函数并让编译器为您完成;它将实现一个浅拷贝,你会确定所有成员数据都被复制,而如果你重载 = op;然后添加一个数据成员然后忘记更新重载就会有问题。
【讨论】:
【参考方案7】:@Alexandre - 我不确定在赋值运算符中传递值。在那里调用复制构造函数有什么好处?这是要固定赋值运算符吗?
附:我不知道如何写cmets。或者可能是我不允许写 cmets。
【讨论】:
通常的参考是cpp-next.com/archive/2009/08/want-speed-pass-by-value。我仍然不相信它的结论总是正确的。 @Steve:对于operator=
,反正你也得抄,不能更糟了。
@Alexandre C:是的,它可以。例如,如果函数没有内联,那么它可能会导致复制构造函数的(调用)更多副本、更大的二进制文件、更多的缓存未命中、更慢。这篇文章提到了这一点,它没有隐藏任何东西,而且我不相信调用者或被调用者是否在所有编译器上执行特定的工作,它从来没有重要。跨度>
【参考方案8】:
如果你有一个工作赋值运算符(复制运算符),技术上没问题。
但是,您应该更喜欢复制和交换,因为:
使用复制交换更容易确保异常安全 最符合逻辑的关注点分离: copy-ctor 是关于分配它需要的资源(复制其他东西)。 交换功能(大部分)仅关于交换内部“句柄”,不需要进行资源(取消)分配 析构函数是关于资源释放的 Copy-and-swap 在赋值/复制操作符中自然地结合了这三个功能【讨论】:
以上是关于根据 operator= 实现复制构造函数的主要内容,如果未能解决你的问题,请参考以下文章