[英]calling copy constructor and operator= for class data members
Below is an example code (for learning purpose only). 以下是示例代码(仅用于学习目的)。 Classes A and B are independent and have copy contructors and operators= .
类A和B是独立的,并且具有复制构造函数和operator =。
class C
{
public:
C(string cName1, string cName2): a(cName1), b(new B(cName2)) {}
C(const C &c): a(c.a), b(new B(*(c.b))) {}
~C(){ delete b; }
C& operator=(const C &c)
{
if(&c == this) return *this;
a.operator=(c.a);
//1
delete b;
b = new B(*(c.b));
//What about this:
/*
//2
b->operator=(*(c.b));
//3
(*b).operator=(*(c.b));
*/
return *this;
}
private:
A a;
B *b;
};
There are three ways of making assignment for data member b. 有三种方法可以为数据成员b赋值。 In fact first of them calls copy constructor.
实际上,它们首先调用复制构造函数。 Which one should I use ?
我应该使用哪一个? //2 and //3 seems to be equivalent.
// 2和// 3似乎是等效的。
I decided to move my answer to answers and elaborate. 我决定将答案移至答案并进行详细说明。
You want to use 2 or 3 because 1 reallocated the object entirely. 您要使用2或3,因为1会完全重新分配对象。 You do all the work to clean up, and then do all the work to reallocate/reinitialized the object.
您需要清理所有工作,然后再进行所有工作以重新分配/重新初始化对象。 However copy assignment:
但是副本分配:
*b = *cb; * b = * cb;
And the variants you used in your code simply copy the data. 您在代码中使用的变体仅复制数据。
however, we gotta ask, why are you doing it this way in the first place? 但是,我们必须问,为什么您首先要这样做?
There are two reasons, in my mind, to have pointers as members of the class. 我认为将指针作为类的成员有两个原因。 The first is using b as an opaque pointer.
第一种是使用b作为不透明指针。 If that is the case, then you don't need to keep reading.
如果真是这样,那么您无需继续阅读。
However, what is more likely is that you are trying to use polymorphism with b. 但是,更有可能的是您尝试将多态与b一起使用。 IE you have classes D and E that inherit from B. In that case, you CANNOT use the assignment operator!
IE中有从B继承的D和E类。在这种情况下,您不能使用赋值运算符! Think about it this way:
这样考虑:
B* src_ptr = new D();//pointer to D
B* dest_ptr = new E();//pointer to E
*dest_ptr = *src_ptr;//what happens here?
What happens? 怎么了?
Well, the compiler sees the following function call with the assignment operator: 好了,编译器看到了使用赋值运算符的以下函数调用:
B& = const B&
It is only aware of the members of B: it can't clean up the no longer used members of E, and it can't really translate from D to E. 它只知道B的成员:它无法清理E不再使用的成员,并且它实际上不能从D转换为E。
In this situation, it is often better to use situation 1 rather than try to decern the subtypes, and use a clone type operator. 在这种情况下,通常最好使用情况1而不是尝试确定子类型并使用克隆类型运算符。
class B
{
public:
virtual B* clone() const = 0;
};
B* src_ptr = new E();//pointer to D
B* dest_ptr = new D();//pointer to E, w/e
delete dest_ptr;
dest_ptr = src_ptr->clone();
It may be down to the example but I actually don't even see why b
is allocated on the heap. 这可能取决于示例,但实际上我什至看不到为什么
b
被分配在堆上。 However, the reason why b
is allocate on the heap informs how it needs to be copied/assigned. 但是,为什么在堆上分配
b
的原因说明了如何复制/分配b。 I think there are three reasons for objects to be allocated on the heap rather than being embedded or allocated on the stack: 我认为将对象分配在堆上而不是嵌入或分配在堆栈上的原因有三个:
std::shared_ptr<T>
. std::shared_ptr<T>
维护对象。 clone()
function from the base class. clone()
函数clone()
。 Since the type of the object assigned from doesn't have to be the same, both copy construction and assignment would actually clone the object. std::unique_ptr<T>
or a custom clone_ptr<T>
which automatically takes care of appropriate cloning of the type. std::unique_ptr<T>
或自定义clone_ptr<T>
,后者会自动处理适当的类型克隆。 In most cases I would actually implement the assignment operator in an identical form, though: 在大多数情况下,我实际上会以相同的形式实现赋值运算符:
T& T::operator=(T other) {
this->swap(other);
return *this;
}
That is, for the actual copy of the assigned object the code would leverage the already written copy constructor and destructor (both are actually likely to be = default
ed) plus a swap()
method which just exchanges resources between two objects (assuming equal allocators; if you need to take case of non-equal allocators things get more fun). 也就是说,对于分配对象的实际副本,代码将利用已经编写的副本构造函数和析构函数(实际上都可能是
= default
ed)加上一个swap()
方法,该方法仅在两个对象之间交换资源(假设分配器相等) ;如果您需要使用非相等分配器,那么事情会变得更加有趣)。 The advantage of implementing the code like this is that the assignment is strong exception safe. 像这样实现代码的好处是,分配是强异常安全的。
Getting back to your approach to the assignment: in no case would I first delete
an object and then allocate the replace. 回到您的分配方法:在任何情况下,我都不会先
delete
对象,然后分配替换对象。 Also, I would start off with doing all the operations which may fail, putting them into place at an appropriate place: 另外,我将从所有可能失败的操作开始,将它们放在适当的位置:
C& C::operator=(C const& c) { std::unique_ptr tmp(new B(*cb)); C&C :: operator =(C const&c){std :: unique_ptr tmp(new B(* cb)); this->a = ca;
this-> a = ca; this->b = tmp.reset(this->b);
this-> b = tmp.reset(this-> b); return *this;
返回* this; }
}
Note that this code does not do a self-assignment check. 请注意,此代码不会做一个自我赋值检查。 I claim that any assignment operator which actually only works for self-assignment by explicitly guarding against is not exception-safe, at least, it isn't strongly exception safe.
我声称,任何实际上仅通过显式防范而仅用于自我分配的赋值运算符都不是异常安全的,至少,它不是绝对异常安全的。 Making the case for the basic guarantee is harder but in most cases I have seen the assignment wasn't basic exception safe and your code in the question is no exception: if the allocation throws,
this->b
contains a stale pointer which can't be told from another pointer (it would, at the very least, need to be set to nullptr
after the delete b;
and before the allocation). 提出基本保证的理由比较困难,但是在大多数情况下,我已经看到分配不是基本的异常安全,问题中的代码也不例外:如果分配抛出,
this->b
包含一个陈旧的指针,可以不能从另一个指针告诉它(至少在delete b;
在分配之前,至少需要将其设置为nullptr
)。
b->operator=(*(c.b));
(*b).operator=(*(c.b));
These two operations are equivalent and should be spelled 这两个操作是等效的,应该拼写
*this->b = *c.b;
or 要么
*b = *c.b;
I prefer the qualified version, eg, because it works even if b
is a base class of template inheriting from a templatized base, but I know that most people don't like it. 我更喜欢限定版本,例如,因为即使
b
是从模板化基础继承的模板的基类,它也可以工作,但是我知道大多数人不喜欢它。 Using operator=()
fails if the type of the object happens to be a built-in type. 如果对象的类型恰好是内置类型,则使用
operator=()
失败。 However, a plain assignment of a heap allocated object doesn't make any sense because the object should be allocated on the heap if that actually does the right thing. 但是,对堆分配的对象进行简单分配没有任何意义,因为如果该对象确实做了正确的事情,则应该在堆上分配该对象。
If you use method 1 your assignment operator doesn't even provide the basic (exception) guarantee so that's out for sure. 如果使用方法1,则赋值运算符甚至不提供基本(例外)保证,因此可以肯定。
Best is of course to compose by value. 最好当然是由价值组成。 Then you don't even have to write your own copy assignment operator and let the compiler do it for you!
然后,您甚至不必编写自己的副本分配运算符,让编译器为您完成!
Next best, since it appears you will always have a valid b
pointer, is to assign into the existing object: *b = *cb;
下一个最好的选择是,将其分配给现有对象,因为它似乎总是有一个有效的
b
指针: *b = *cb;
a = c.a;
*b = *c.b;
Of course, if there is a possibility that b
will be a null pointer the code should check that before doing the assignment on the second line. 当然,如果
b
可能是空指针,则代码应在第二行赋值之前进行检查。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.