go to start Ex W3
|home |print view |recent changes |changed March 8, 2018 |
exact
|You are 54.80.93.19 <- set your identity!

Sections: Tracer Again | Output of =[Tracer]= | Tracer in Standard Container | Copy Assignment | Move Construction/Assignment | Vector Again | Moving vs. Copying Large Objects |

Tracer Again ^

From CPlusPlus you might already be familiar with the tracer class as used in the extra exercises. Today it's not extra anymore.

IMPORTANT: Since the released versions of GCC (7.2.0) and Clang (5.0.0) seem to be actually creating a function for deduction guides, you should refrain from relying on class template argument deduction for this exercise! Otherwise, you might observe additional copies, which are not neccessary. For this exercise: Prefer std::vector<Tracer> over std::vector.

#include <iostream>
#include <string>

struct Tracer {
  explicit Tracer(std::string name = "") : name { name } {
    std::cout << "Tracer created: " << name << std::endl;
  }
  ~Tracer() {
    std::cout << "Tracer destroyed: " << name << std::endl;
  }
  Tracer(Tracer const& other) : name { other.name + " copy" } {
    std::cout << "Tracer copied: " << name << std::endl;
  }
  void show() const {
    std::cout << "Tracer: " << name << std::endl;
  }
  std::string name;
};

void foo(Tracer t) {
  Tracer trace{"foo"};
  t.show();
}

Tracer bar(Tracer const &t) {
  Tracer trace{"bar"};
  t.show();
  return trace;
}

int main() {
  Tracer m{"main"};
  {
    Tracer inner{"inner"};
    foo(inner);
    auto trace = bar(inner);
    trace.show();
    inner.show();
  }
  foo(Tracer{"temp"});
  m.show();
}

Output of Tracer ^

Look at the code of the Tracer program and figure out what output it generates and why.

Tracer in Standard Container ^

Observe the behavior of the Tracer type when used in an std::vector.

  std::vector<Tracer> v{};
  v.push_back(Tracer{"T1"});
  v.push_back(Tracer{"T2"});
  v.push_back(Tracer{"T3"});
  v.push_back(Tracer{"T4"});

  std::vector<Tracer> v_copy{v};

Copy Assignment ^

Your Tracer type does not provide an assignment operator. Add one which adds " copy-assigned" to the name and prints a notification about the copy operation on std::cout, similar to the implemented constructors.

int main() {
  ...
  std::cout << "\t\t--- creating sink and source ----" << std::endl;
  Tracer sink {"sink"}, source {"source"};

  std::cout << "\t\t--- assigning source to sink ----" << std::endl;
  sink = source;

  std::cout << "\t\t--- showing sink ----" << std::endl;
  sink.show();

  std::cout << "\t\t--- showing source ----" << std::endl;
  source.show();

  std::cout << "\t\t--- end of main ----" << std::endl;
}

Move Construction/Assignment ^

Replace the copy constructor and copy-assignment operator of the tracer class with a move constructor and a move-assignment operator:

Afterwards, target shall contain the name "orgin moved" and origin shall contain the name "origin moved away".

Afterwards, target shall contain the name "origin move-assigned" and origin shall contain the name "target moved to".

Hint: To avoid creating a temporary name in the move assignment operator, use std::swap to exchange the names of the objects first: std::swap(name, other.name);.

What about the state of source after the move assignment?

As you have a moveable type what happens if you just call std::move on a tracer object? What is the output of the call below? Why?

int main() {
  ...
  std::cout << "\t\t--- std::move(m) ----" << std::endl;
  std::move(m);
  std::cout << "\t\t--- end of main ----" << std::endl;
}

Vector Again ^

Now as your Tracer is movable:

Moving vs. Copying Large Objects ^

Large objects can benefit from move operations. An std::vector, for example, allocates memory on the heap for storing its values. Copying a vector requires additional heap space and copies all values to this new memory location. Moving a vector just needs to move the pointers, which access this memory, to the move-constructed/assigned vector.

Try to measure the difference in allocating, copying and moving a large std::vector.

You can use std::chrono to measure the operations.

  auto start = std::chrono::system_clock::now();
  //allocate large vector
  std::chrono::duration<double> delta = std::chrono::system_clock::now() - start;
  std::cout << "creating the container took: " << delta.count() << "s time\n";

What size of vector is large enough to observe the behavior?



|home |print view |recent changes |changed March 8, 2018 |
exact
|You are 54.80.93.19 <- set your identity!

Ex W3
go to start