简体   繁体   English

为类数据成员调用副本构造函数和operator =

[英]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: 我认为将对象分配在堆上而不是嵌入或分配在堆栈上的原因有三个:

  1. The object is shared between multiple other objects. 该对象在多个其他对象之间共享。 Obviously, in this case there is shared ownership and it isn't the object which is actually copied but rather a pointer to the object. 显然,在这种情况下,存在共享所有权,并且不是实际复制的对象,而是指向该对象的指针。 Most likely the object is maintained using a std::shared_ptr<T> . 最有可能使用std::shared_ptr<T>维护对象。
  2. The object is polymorphic and the set of supported types is unknown. 该对象是多态的,支持的类型集未知。 In this case the object is actually not copied but rather cloned using a custom, virtual 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. 由于从中分配的对象的类型不必相同,因此复制构造和分配实际上都会克隆该对象。 The object is probably held using a 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> ,后者会自动处理适当的类型克隆。
  3. The object is too big to be embedded. 该对象太大,无法嵌入。 Of course, that case doesn't really happen unless you happen to implement the large object and create a suitable handle for it. 当然,除非您恰好实现了大对象并为其创建了合适的句柄,否则这种情况不会真正发生。

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.

相关问题 没有指针数据成员的类-复制构造函数==赋值运算符? - Class with no pointer data members- copy constructor == assignment operator? 您应该在复制构造函数或赋值运算符中复制静态数据成员吗? - Should you copy static data members in a copy constructor or assignment operator? 如何在赋值运算符中处理引用数据成员,并复制构造函数? - How to deal with reference data-members in the assignment operator, and copy constructor? 在赋值运算符中调用复制构造函数 - calling copy constructor in assignment operator 在复制构造函数中调用赋值运算符 - Calling assignment operator in copy constructor 使用shared_ptr数据成员复制类的构造函数? - Copy constructor for class with shared_ptr data members? 没有数据成员和大括号语法的类的缺省拷贝构造函数 - Default copy constructor for a class without data members and brace syntax 使用复制构造函数时,类数据成员是否在复制构造函数之前初始化? - Are class data members initialized before the copy constructor when using the copy constructor? 复制构造函数调用另一个类的复制构造函数 - Copy constructor calling copy constructor of another class 复制构造函数,operator= 在子 class - copy constructor, operator= in a child class
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM