for Robot Artificial Inteligence

9. Smart pointers, Associative containers, type casting, Enumeration classes, Read/write binary files.

|

Smart pointers

  • Smart pointers wrap a raw pointer into a class and manage its lifetime (RAII)
  • Smart pointers are all about ownership
  • Always use smart pointers when the pointer should own heap memory
  • Only use them with heap memory!
  • Still use raw pointers for non-owning pointers and simple address storing
  • #include to use smart pointers
  • We will focus on 2 types of smart pointers:
    • std::unique_ptr
    • std::shared_ptr

Smart pointers manage memory!

  • Smart pointers apart from memory allocation behave exactly as raw pointers:
    • Can be set to nullptr
    • Use * ptr to dereference ptr
    • Use ptr-> to access methods
    • Smart pointers are polymorphic
  • Additional functions of smart pointers:
    • ptr.get() returns a raw pointer that the smart pointer manages
    • ptr.reset(raw_ptr) stops using currently managed pointer, freeing its memory if needed, sets ptr to raw_ptr

Unique pointer (std::unique_ptr)

  • Constructor of a unique pointer takes ownership of a provided raw pointer
  • No runtime overhead over a raw pointer
  • Syntax for a unique pointer to type Type:
    #include <memory>
    // Using default constructor Type();
    auto p = std :: unique_ptr <Type >(new Type);
    // Using constructor Type(<params >);
    auto p = std :: unique_ptr <Type >(new Type(<params >));
    
  • From C++14 on:
    // Forwards <params > to constructor of unique_ptr
    auto p = std :: make_unique <Type>(<params >);
    

What makes it “unique”

  • Unique pointer has no copy constructor
  • Cannot be copied, can be moved
  • Guarantees that memory is always owned by a single unique pointer
#include <iostream>
#include <memory>
struct A{
  int a = 10;
};
int main () {
  auto a_ptr = std::unique_ptr<A>(new A); // a_ptr are pointing a A structe
  std::cout << a_ptr ->a << std::endl;
  auto b_ptr = std::move(a_ptr);
  std::cout << b_ptr ->a << std::endl;
  return 0;
}

Shared pointer (std::shared_ptr)

  • Constructed just like a unique_ptr
  • Can be copied
  • Stores a usage counter and a raw pointer
    • Increases usage counter when copied
    • Decreases usage counter when destructed
  • Frees memory when counter reaches 0
  • Can be initialized from a unique_ptr
#include <memory>
// Using default constructor Type();
auto p = std::shared_ptr<Type >(new Type);
auto p = std::make_shared <Type >(); // Preferred
// Using constructor Type(<params >);
auto p = std::shared_ptr<Type >(new Type(<params>));
auto p = std::make_shared <Type>(<params>); // Preferred

Shared pointer

#include <iostream>
#include <memory>
struct A{
  A(int a) { std::cout << "I'm alive!\n"; }
  ~A() { std::cout << "I'm dead... :(\n"; }
};
int main(){
  // Equivalent to: std::shared_ptr <A>(new A(10));
  auto a_ptr = std :: make_shared <A >(10);
  std::cout << a_ptr.use_count() << std::endl;//1
  {
    auto b_ptr = a_ptr;
    std::cout << a_ptr.use_count() << std::endl; //2
  }
  std :: cout << "Back to main scope\n";
  std :: cout << a_ptr. use_count () << std :: endl;
  return 0;
}
  • result
I'm alive
1
2
Back to main scope
1
i'm dead...

When to use what?

  • Use smart pointers when the pointer must manage memory
  • By default use unique_ptr
  • If multiple objects must share ownership over something, use a shared_ptr to it
  • Using smart pointers allows to avoid having destructors in your own classes
  • Think of any free standing new or delete as of a memory leak or a dangling pointer:
    • Don’t use delete
    • Allocate memory with make_unique, make_shared
    • Only use new in smart pointer constructor if cannot use the functions above

Typical beginner error

#include <iostream>
#include <memory>
int main () {
  int a = 0;
  // Same happens with std::shared_ptr.
  auto a_ptr = std::unique_ptr<int>(&a);
  return 0;
}
  • return
    *** Error in `file ': free ():
    2 invalid pointer: 0 x00007fff30a9a7bc ***
    
  • Create a smart pointer from a pointer to a stack-managed variable
  • The variable ends up being owned both by the smart pointer and the stack and gets deleted twice → Error!

good exmple using smart pointers

#include <iostream>
#include <vector>
#include <memory>
using std :: cout; using std :: unique_ptr ;
struct AbstractShape { // Structs to save space.
  virtual void Print () const = 0;
};
struct Square : public AbstractShape{
  void Print () const override { cout << "Square\n"; }
};
struct Triangle : public AbstractShape{
  void Print () const override { cout << "Triangle\n"; }
};
int main(){
  std::vector<unique_ptr<AbstractShape>> shapes;
  shapes.emplace_back (new Square);
  auto triangle = unique_ptr<Triangle>(new Triangle);
  shapes.emplace_back(std::move(triangle));
  for (const auto& shape : shapes) { shape ->Print (); }
  return 0;
}

Associative containers - std::map

  • #include <map> to use std::map
  • Stores items under unique keys
  • Implemented usually as a Red-Black tree
  • Key can be any type with operator < defined
  • Create from data:
    std ::map <KeyT , ValueT > m = {
    {key , value}, {key , value}, {key , value }};
    
  • Add item to map: m.emplace(key, value);
  • Modify or add item: m[key] = value;
  • Get (const) ref to an item: m.at(key);
  • Check if key present: m.count(key) > 0;
  • Check size: m.size();

std::unordered_map

  • **#include ** to use **std::unordered_map**
  • Serves same purpose as std::map
  • Implemented as a hash table
  • Key type has to be hashable
  • Typically used with int, string as a key
  • Exactly same interface as std::map

Iterating over maps

for (const auto& kv : m) {
  const auto& key = kv.first;
  const auto& value = kv.second;
  // Do important work.
}
  • Every stored element is a pair
  • map has keys sorted
  • unordered_map has keys in random order

Type casting(角色分配) - Casting type of variables

  • Every variable has a type
  • Types can be converted from one to another
  • Type conversion is called type casting
  • There are 3 ways of type casting:
    • static_cast
    • reinterpret_cast
    • dynamic_cast

      static_cast

  • Syntax: static_cast(variable)
  • Convert type of a variable at compile time
  • Rarely needed to be used explicitly
  • Can happen implicitly for some types,
    • e.g. float can be cast to int
  • Pointer to an object of a Derived class can be upcast to a pointer of a Base class
  • Enum value can be caster to int or float
  • Full specification is complex!

reinterpret_cast

  • Syntax: reinterpret_cast(variable)
  • Reinterpret the bytes of a variable as another type
  • We must know what we are doing!
  • Mostly used when writing binary data

dynamic_cast

  • Syntax: dynamic_cast<Base*>(derived_ptr)
  • Used to convert a pointer to a variable of Derived type to a pointer of a Base type
  • Conversion happens at runtime
  • If derived_ptr cannot be converted to Base* returns a nullptr
  • returns a nullptr

Enumeration classes

  • Store an enumeration of options
  • Usually derived from int type
  • Options are assigned consequent numbers
  • Mostly used to pick path in switch
    enum class EnumType { OPTION_1 , OPTION_2 , OPTION_3 };
    
  • Use values as:
    • EnumType::OPTION_1, EnumType::OPTION_2, …
  • GOOGLE-STYLE Name enum type as other types, CamelCase
  • GOOGLE-STYLE Name values as constants kSomeConstant or in ALL_CAPS
#include <iostream>
#include <string>
using namespace std;
enum class Channel{STDOUT, STDERR};
void Print(Channel print_style, const string& msg){
  switch (print_style){
    case Channel::STDOUT:
      cout << msg << endl;
      break;
    case Channel::STDERR:
      cout << msg << endl;
      break;
    default:
      cerr << "Skipping\n";
  }
}

int main () {
  Print(Channel::STDOUT, "hello");
  Print(Channel::STDERR, "World");
}

Explicit values

  • By default enum values start from 0
  • We can specify custom values if needed
  • Usually used with default values
    enum class EnumType {
    OPTION_1 = 10, // Decimal.
    OPTION_2 = 0x2 , // Hexacedimal.
    OPTION_3 = 13
    };
    

Read/write binary files - Writing to binary files

  • We write a sequence of bytes
  • We must document the structure well, otherwise noone can read the file
  • Writing/reading is fast
  • No precision loss for floating point types
  • Substantially smaller than ascii-files
  • Syntax
    file.write(reinterpret_cast <char*>(&a), sizeof(a));
    

Writing to binary files

#include <fstream> // for the file streams
#include <vector>
using namespace std;
int main(){
  string file_name = "image.dat";
  ofstream file(file_name ,
                ios_base :: out | ios_base :: binary);
  if (! file) { return EXIT_FAILURE ; }
  int r = 2; int c = 3;
  vector<float> vec(r * c, 42);
  file.write(reinterpret_cast <char * >(&r), sizeof(r));
  file.write(reinterpret_cast <char*>(&c), sizeof(c));
  file.write(reinterpret_cast <char*>(& vec.front ()),
            vec.size () * sizeof(vec.front ()));
  return 0;
}

Reading from binary files

  • We read a sequence of bytes
  • Binary files are not human-readable
  • We must know the structure of the contents
  • Syntax
    • file.read(reinterpret_cast <char*>(&a), sizeof(a));

Reading from binary files

#include <fstream>
#include <iostream>
#include <vector>
using namespace std;
int main(){
  string file_name = "image.dat";
  int r = 0, c = 0;
  ifstream in(file_name , ios_base ::in | ios_base :: binary);
  if (!in) { return EXIT_FAILURE ; }
  in.read(reinterpret_cast <char*>(&r), sizeof(r));
  in.read(reinterpret_cast <char*>(&c), sizeof(c));
  cout << "Dim: " << r << " x " << c << endl;
  vector <float > data(r * c, 0);
  in.read(reinterpret_cast <char*>(& data.front ()), data.size () * sizeof(data.front ()));
  for (float d : data) { cout << d << endl; }
  return 0;
}

Reference

https://www.ipb.uni-bonn.de/teaching/modern-cpp/

Cpp Core Guidelines: https://github.com/isocpp/CppCoreGuidelines

Git guide: http://rogerdudler.github.io/git-guide/

C++ Tutorial: http://www.cplusplus.com/doc/tutorial/

Book: Code Complete 2 by Steve McConnell

Modern CMake Tutorial https://www.youtube.com/watch?v=eC9-iRN2b04

Compiler Explorer: https://godbolt.org/ Gdbgui: https://www.gdbgui.com/ CMake website: https://cmake.org/ Gdbgui tutorial: https://www.youtube.com/watch?v=em842geJhfk

Fluent C++: structs vs classes: https://google.github.io/styleguide/cppguide.html#Structs_vs._Classes

Comments