Lecture 10
Note: Careful with constructors that can take one param
struct Node {
  ...
  Node (int data):
    data{data}, next{nullptr};
}- single-arg constructors create implicit conversions eg. - Node n{4}
- but also - Node n=4;- implicit concersion from int to Node 
 
int f(Node n);
f(4); // works - 4 implicitly converted to Node
Node m{4}; // works fine
Node m = 4; // works - but 4 is not a node, it is an int DANGER:
- accidentally passing an int to a function expecting a Node - silent conversion 
- compiler does not signal an error 
- potential errors are not caught 
 
How to fix
- Disable the implicit conversion - make constructor explicit 
struct Node {
  ...
  explicit Node (int data): data{data}, next{nullptr} {}
}
Node n{4}; // OK!
Node n=4; // not allowed
f(4); // not allowed
f(Node {4}); // OK!Destructors
When an object is destroyed:
- stack-allocatd: goes out of scope 
- heap-allocated: is deleted 
- A method called the desctructor is run 
- Classes come with a destructor (just calls destructors for all fields that are objects) 
What happens when an object is destroyed?
- destructor body runs 
- fields' destructors are invoked in reverse declaration order (for fields that are objects) 
- space deallocated 
When do we need to write a destructor?
Node *np=new Node {1, new Node {2, new Node {3, nullptr}}};If np goes out of scope
- pointer np is reclaimed (stack-allocated) 
- the list is leaked 
If we say delete np;:
Write a destructor to ensure the whole list is freed:
struct Node {
  ...
  ~Node() {
    delete next;
  }
}Now - delete np; frees the whole list
- invokes *next's destructors using recursion 
- therefore, the whole list is deallocated 
Copy Assignment Operator
Student billy {60,70,80};
Student jane=billy; // copy constructor
Student joey; // default constructor
joey = billy; // copy, but not a construction, it is the copy assignment operator -uses compiler-supplied default- You may need to write your own copy assignment operator: 
// WRONG AND DANGEROUS WAY:
struct Node {
  ...
  // so that cascading works (eg. a=b=c=d;)
  Node &operator = (const Node &other) {
    data = other.data;
    next=other.next?new Node{*other.next*}:nullptr;
    return *this;
  }
}Why is this way dangerous?
Node n {1, new Node {2, new Node {3, nullptr}}};
// deletes n and then tries to copy n to n
// undefined behaviour
n=n; When writing operator=, always watch out for self-assignment:
struct Node {
  ...
  Node &operator=(const Node &other) {
    if (this == &other) return *this;
    data = other.data;
    delete next;
    
    // if "new" fails, this method will abort
    // next has been deleted, but not reassigned, therefore we have pointers at dead memory which means we have a corrupted list
    next = other.next? new Node {*other, next}: nullptr;
    return *this;
  }
}Better Way:
Node &Node::operator=(const Node &other) {
  if (this == &other) return *this;
  Node *tmp = next;
  next = other.next? new Node {*other.next}:nullptr;
  data = other.data
  delete tmp;
  return *this; // if new fails, node will still be in its original state (not corrupted)
}Note:
*other.next // equivalent to *(other.next)
other->next // equivalent to (*other).nextAlternative: copy-and-swap idiom
#include <utility>
struct Node {
  ...
  void swap (Node &other) {
    using std::swap;
    swap(data, other.data);
    swap(next, other.next);
  }
  Node &operator = (const Node &other) {
    Node tmp = other;
    swap(tmp);
    return *this;
  }
};Rvalues and Rvalue references
Recall:
- an lvalue is anything with an address 
- an lvalue ref (&) is like a const pointer with auto-deret - always initialized to an lvalue 
 
Now consider:
Node plusOne (Node n) {
  for (Node *p=&n; p; p=p->next) {
    ++p->data;
  }
  return n;
}
Node n {1, new Node {2, nullptr}};
Node n2 = plusOne(n); //copy construction- compiler creates a temporary object to hold the result of plusOne 
- other is a reference to this temporary - copy constructor deep-copies from this tmeporary 
 
But
Last updated
Was this helpful?
