C++ Examples

C++ Examples

std::vector

  • std::vector<int> iv by itself is on the stack, but its data is on the heap

  • sizeof(std::vector<int>) is 24; it contains three 8-bytes pointers (on 64-bit machines)

    • one pointing to the beginning of the data on the heap

    • one pointing to the end of the data (size)

    • one pointing to the end of the allocated memory (capacity)

  • starting from empty, the capacity can grow to 1 2 4 8 16 ... under gcc compiler; it may differ under other compilers (e.g., MSVC)

  • essentially is a dynamic array: O(1) to insert at the end, but O(n) to insert elsewhere

std::vector<int> v; 
for(std::size_t i=0; i<10; ++i){
    v.push_back(i);
    std::cout << v.size() << " " << v.capacity() << std::endl;
}

for(auto i=v.begin(); i!=v.end(); ++i){
    std::cout << &*i << std::endl;  // print the address of each element
}

references: https://stackoverflow.com/questions/8036474/when-vectors-are-allocated-do-they-use-memory-on-the-heap-or-the-stack; https://stackoverflow.com/questions/12271017/initial-capacity-of-vector-in-c; https://chryswoods.com/beginning_c++/lists.html;

static_cast and dynamic_cast

The static cast performs conversions between compatible types. It is similar to the C-style cast, but is more restrictive. For example, the C-style cast would allow an integer pointer to point to a char.

char c = 10;       // 1 byte
int *p = (int*)&c; // 4 bytes

Since this results in a 4-byte pointer pointing to 1 byte of allocated memory, writing to this pointer will either cause a run-time error or will overwrite some adjacent memory.

*p = 5; // run-time error: stack corruption

In contrast to the C-style cast, the static cast will allow the compiler to check that the pointer and pointee data types are compatible, which allows the programmer to catch this incorrect pointer assignment during compilation.

int *q = static_cast<int*>(&c); // compile-time error

reference: https://stackoverflow.com/questions/28002/regular-cast-vs-static-cast-vs-dynamic-cast/18414172#18414172;

template and typename

  • typename needed by dependent names

// example in a templated class
typedef pcl::PointCloud<PointInT> PointCloudIn;
typedef typename PointCloudIn::Ptr PointCloudInPtr;  // need typename due to Ptr
typedef typename PointCloudIn::ConstPtr PointCloudInConstPtr; // need typename

// example in a templated function
template <typename PointT> // needed for templated argument type dependent on PointT
void func(typename pcl::PointCloud<PointT>::Ptr& cloud){
  // if creating a new instance dependent on PointT
  typename pcl::KdTreeFLANN<PointT>::Ptr tree;
}

// example error message without typename keyword
//// error: need ‘typename’ before ‘pcl::KdTreeFLANN<PointT>::Ptr’ 
//// because ‘pcl::KdTreeFLANN<PointT>’ is a dependent scope

// one more example
template <typename FeatureT>
class MyPerception
{
  public:
    using PointT = pcl::PointXYZRGBNormal;
    using NormalT = pcl::PointXYZRGBNormal;

    using PointCloudPtr = pcl::PointCloud<PointT>::Ptr;         // not needed
    using KeypointPtr = pcl::PointCloud<PointT>::Ptr;           // not needed
    using FeaturePtr = typename pcl::PointCloud<FeatureT>::Ptr; // needed
};

reference: https://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords/17579889#17579889

  • template keyword is needed to tell the compiler explicitly that .cast is a template

// In PCL gicp.hpp
// I would like to save the transformation matrix in each iteration for visualization
rigid_transformation_estimation_(output, source_indices, *target_, target_indices, transformation_);
transformations_.push_back(transformation_.template cast<double>());

references: Stackoverflow: When casting Eigen matrix type, error: expected primary-expression before ‘float’

const

class MyClass {
  public:
    MyClass (): point(0.1) {};
    typedef boost::shared_ptr<MyClass> Ptr;
    typedef boost::shared_ptr<const MyClass> ConstPtr;
    void mem_func (MyClass::Ptr ptr) const;
    float point;
};

// not allowed to change the member variables of MyClass::Ptr (which is the raw pointer)
void func(const MyClass::Ptr ptr){
  ptr->point = 1.0; // OK
  ptr.reset();      // error: not able to reset to null or to other instance
}

// not allowed to change the member variables of MyClass
void func(MyClass::ConstPtr ptr){
  ptr->point = 1.0; // error: not able to change the value
  ptr.reset();      // OK
}

void MyClass::mem_func(MyClass::Ptr ptr) const {
  point = 1.0;        // error: not allowed to change member variables
  this->point = 1.0;  // error: not allowed to change member variables
  ptr->point = 1.0;   // OK  (external pointer)
}

mutable

Recall that the const reference or pointer is constrained to

  • only read access for any visible data members,

  • permission to call only methods that are marked as const (because non-const member functions can write to data members, which breaks the fence).

The mutable keyword can allow you to modify a variable in a const method. It is like making exceptions for a few special variables.

class Foo {
  private:
    mutable bool done_;
  public:
    void doSomething() const { ...; done_ = true; }
};

references: https://stackoverflow.com/questions/105014/does-the-mutable-keyword-have-any-purpose-other-than-allowing-the-variable-to;

reference

void func (MyClass::Ptr ptr){
  std::cout << "use_count() = " << ptr.use_count() << std::endl; // 2
}

void func_ref (MyClass::Ptr& ptr){
  std::cout << "use_count() = " << ptr.use_count() << std::endl; // 1
}

int main(){
  MyClass::Ptr pointer (new MyClass);
  std::cout << "use_count() = " << pointer.use_count() << std::endl; // 1
  func (pointer);
  func_ref (pointer);
}

virtual function (for runtime polymorphism)

// flann/algorithms/nn_index.h
virtual int knnSearch(const Matrix<ElementType>& queries,
                            Matrix<size_t>& indices,
                            Matrix<DistanceType>& dists,
                            size_t knn,
                      const SearchParams& params) const
{
  // implementation
}

int knnSearch(const Matrix<ElementType>& queries,
                    Matrix<int>& indices,
                    Matrix<DistanceType>& dists,
                    size_t knn,
              const SearchParams& params) const
{
	flann::Matrix<size_t> indices_(new size_t[indices.rows*indices.cols], indices.rows, indices.cols);
	int result = knnSearch(queries, indices_, dists, knn, params);
  // wrapper
}

templated classes in cpp/hpp files & explicit instantiation

  • behavior: the templated classes can only be implemented in hpp files, but not cpp files

  • workaround: add explicit instantiation in the cpp files

  • reason in short: a template is literally a template; a class template is not a class, it's a recipe for creating a new class for each T we encounter

references: splitting templated classes into cpp/hpp files; why can templated only be implemented in the header file; explicit template instantiation when is used

smart pointer vs. raw pointer

pcl::PointCloud<PointT> cloud;  // instance
pcl::PointCloud<PointT>::Ptr cloud_ptr (new pcl::PointCloud<PointT>); //smart pointer
auto cloud_raw_ptr = new pcl::PointCloud<PointT>;  // raw pointer 

*cloud_ptr      // instance
*cloud_raw_ptr  // instance   typeid(*cloud_ptr) == typeid(*cloud_raw_ptr)
&cloud          // raw pointer
&(*cloud_ptr)   // will turn the smart pointer into a raw pointer

std::cout << typeid(*cloud_ptr).name() << std::endl;

iterator

There are five types of iterator available in STL.

  • Bidirectional Iterator available for std::list,std::set and std::map

  • Random Access Iterator available for std::vector and std::deque

  • No iterator available for std::stack, std::queue and std::priority_queue

std::vector<int> vec{1, 2, 3, 4, 5};
std::vector<int>::iterator it;
for (it = vec.begin(); it < vec.end(); ++it)
    std::cout << *it << "\n";
// begin() points to the first element in the container
// end() points to the element after the last element (empty)

function pointer

// in keypoint.h
typedef boost::function<int (int, double, std::vector<int> &, std::vector<float> &)> SearchMethod;

// class Search in search.h
virtual int
radiusSearch (int index, double radius, std::vector<int> &k_indices,
              std::vector<float> &k_sqr_distances, unsigned int max_nn = 0) const;

// in pcl::Keypoint<PointInT, PointOutT>::initCompute ()
int (KdTree::*radiusSearch)(int index, double radius, std::vector<int> &k_indices,
        std::vector<float> &k_distances, unsigned int max_nn) const = &KdTree::radiusSearch;
search_method_ = boost::bind (radiusSearch, boost::ref (tree_), _1, _2, _3, _4, 0);
// boost::ref is useful for passing references to function templates (algorithms)
// that would usually take copies (values) of their arguments.

// the simplest function pointer
void (*fun_ptr)(int) = &fun; // fun_ptr is a pointer to function fun() 

reference: Stackoverflow: how to use boost bind with a member function; GeeksForGeeks: function pointer in C; Function Pointers in C and C++

ostream

  • keep in mind: streams are not copyable, but are movable

    • so that the copy constructor of any object that owns a stream won't work

    • because if a class has a non-copyable member then the containing class is also non-copyable

#include <iostream>
#include <fstream>

void myprogram(std::ostream& os) {   // not const
    os << "Hi" << std::endl;
}

int main() {
    myprogram(std::cout);
    std::ofstream ofs("example.txt");  // constructor opens the file
    if(ofs.is_open())
        myprogram(ofs);
    return 0;                          // destructor closes the file
}

reference: Sending cout to a log file

template specialization

  • Explicit (full) template specialization: Allows customizing the template code for a given set of template arguments.

  • Partial template specialization: Allows customizing class [and variable (since C++14)] templates for a given category of template arguments.

// 1) Template declaration (no specialization)
template <class Key, class Value>
struct Foo {};

// 2) Partial specialization
template <class Key>
struct Foo<Key, int> {};

// 3) Full/explicit specialization
template <>
struct Foo<std::string, int> {};

// Then, when instantiating the templates, 
// the compiler will choose the most specialized definition available:
Foo<std::string, std::string> f1; // Should match #1
Foo<int,         int>         f2; // Should match #2
Foo<std::string, int>         f3; // Should match #3
// #1 and #3 work for template functions as well.

// Examples in OpenCV (traits.hpp)
template<typename _Tp> class DataType
{
public:
    typedef _Tp         value_type;
    ...
};
template<> class DataType<bool> { ... };
template<> class DataType<uchar>  { ... };
template<> class DataType<char>  { ... };
...

references: Stackoverflow: Template specialization with empty brackets and struct; cppreference: Explicit (full) template specialization; cppreference: Partial template specialization; include/opencv2/core/traits.hpp

enum vs union

  • Enum: helps to assign constants to a set of names to make program easier to read, maintain and understand

    • Enums are not actual variables; they're just a semi-type-safe form of #define. They're a way of storing a number in a reader-friendly format. The compiler will transform all uses of an enumerator into the actual numerical value.

  • Union: helps to store data of different types as a single unit

// Enum example in OpenCV (traits.hpp)
// template<> class DataType<bool>
enum { generic_type = 0,
       depth        = CV_8U,
       channels     = 1,
       fmt          = (int)'u',
       type         = CV_MAKETYPE(depth, channels)
     };

// Enum example in point cloud registration
enum class RegistrationMethod { PCL, MTPCL, FAST };
struct RegistrationResult {
  RegistrationMethod method;
  double time_elapsed;
  double fitness;
  Eigen::Matrix4d transformation;
};

// Union example in PCL (point_types.hpp)
// PointXYZLAB
union
{
  struct
  {
    float L;
    float a;
    float b;
  };
  float data_lab[4];
};

references: Stackoverflow: what is the size of an enum type data in C++?;

return multiple values

For returning two values we can use a std::pair (usually typedef'd). In C++11 and newer, there's std::tuple for more than two return results. With introduction of structured binding in C++17, returning std::tuple should probably become accepted standard.

In practice, if multiple result values are needed, the best practice is to create a struct to store these values and return this struct or change it as the pass-by-reference argument.

// in C++ 11
#include <tuple>
std::tuple<int, int> divide(int dividend, int divisor) {
    return std::make_tuple(dividend / divisor, dividend % divisor);
}
#include <iostream>
int main() {
    int quotient, remainder;
    std::tie(quotient, remainder) = divide(14, 3);
}
// Example in Open3D
// open3d/pipelines/registration/ColoredICP.cpp
Eigen::Matrix6d JTJ;
Eigen::Vector6d JTr;
double r2;
std::tie(JTJ, JTr, r2) =
        utility::ComputeJTJandJTr<Eigen::Matrix6d, Eigen::Vector6d>(
                compute_jacobian_and_residual, (int)corres.size());
// open3d/utility/Eigen.cpp
template <typename MatType, typename VecType>
std::tuple<MatType, VecType, double> ComputeJTJandJTr(
        std::function<void(int, VecType &, double &, double &)> f,
        int iteration_num,
        bool verbose /*=true*/) { ... }
// Example in TEASER++
namespace teaser {
struct RegistrationSolution {
  bool valid = true;
  double scale;
  Eigen::Vector3d translation;
  Eigen::Matrix3d rotation;
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
}

references: StackOverflow: Returning multiple values from a C++ function; Open3D/ColoredICP; TEASER-plusplus/include/teaser/registration.h

std::thread

  • template< class Function, class... Args > explicit thread( Function&& f, Args&&... args );

  • Move constructor is allowed, but copy constructor is deleted.

  • If running class member functions, an instance of the class is needed as the first argument.

// Example from slambook2 ch13
Viewer::Viewer() {
    viewer_thread_ = std::thread(std::bind(&Viewer::ThreadLoop, this));
}
void Viewer::Close() {
    viewer_running_ = false;
    viewer_thread_.join();
}

// Example from LeGO-LOAM mapOptimization
int main(int argc, char** argv) {
    ros::init(argc, argv, "lego_loam");
    mapOptimization MO;
    std::thread loopthread(&mapOptimization::loopClosureThread, &MO);
    std::thread visualizeMapThread(&mapOptimization::visualizeGlobalMapThread, &MO);
    ros::Rate rate(200);
    while (ros::ok()) {
        ros::spinOnce();
        MO.run();
        rate.sleep();
    }
    loopthread.join();
    visualizeMapThread.join();
    return 0;
}

// Example from A-LOAM laserMapping
void process() {
    while(1) { ... }
}
int main(int argc, char **argv) {
    ros::init(argc, argv, "laserMapping");
    std::thread mapping_process{process};
    ros::spin();
    return 0;
}

references: C++ reference on std::thread constructor; slambook2 chapter 13 source code; LeGO-LOAM map optimization source code; A-LOAM mapping source code;

volatile

  • It is a keyword to tell compiler that this variable may be modified from outside the program which compiler is not aware of. Therefore, please do not optimize this variable.

  • It is a qualifier that can also be applied to methods/functions.

  • volatile and const are two faces of the same coin.

// Example without volatile
int some_int = 100;
while (some_int == 100) { // compilier may optimize this to while(true)
   // code not related to some_int
}

// Example with volatile
volatile int some_int = 100; // tells compiler not to optimize this variable
while (some_int == 100) {
   // code not related to some_int
}

references: C++ keywords: volatile; https://stackoverflow.com/questions/4437527/why-do-we-use-volatile-keyword; https://stackoverflow.com/questions/4479597/does-making-a-struct-volatile-make-all-its-members-volatile/4479652;

memory fence/barrier

  • This refers to a set of instructions to synchronize memory access (read/writes occur in the order you expect). For example a 'full fence' means all read/writes before the fence are comitted before those after the fence. It is a hardware level concept, and in higher level languages we are used to dealing with mutexes and semaphores.

  • Note that the keyword volatile does not guarantee a memory barrier to enforce cache-consistency. Although It is guaranteed that volatile reads/writes will happen in the exact order specified in the source code, but volatile reads/writes can still be reordered with respect to non-volatile ones. (This is referred to as compiler reordering optimizations.)

Thread #1 Core #1:

 while (f == 0);
 // Memory fence required here
 print x;

Thread #2 Core #2:

 x = 42;
 // Memory fence required here
 f = 1;

references: https://stackoverflow.com/questions/1525189/do-i-need-a-mutex-for-reading; https://en.wikipedia.org/wiki/Memory_barrier; https://stackoverflow.com/questions/286629/what-is-a-memory-fence;

hash

By default, std::pair and std::tuple are not hashable, and therefore they cannot serve as keys in std::unordered_map. For hashing and combining multiple values, one way is to use boost::hash_combine function. Alternatively, we can write our own hash functions.

/**
 * @brief Templated hash function for Eigen::Matrix class.
 * 
 * Example usage:
 *   std::unordered_map<Eigen::Vector3i, double, hash_matrix<Eigen::Vector3i>>
 * 
 * @note This implementation is oblivious to the storage order of Eigen matrix 
 * (column- or row-major). It will give you the same hash value for two different
 * matrices if they are the transpose of each other in different storage order.
 * 
 *  The code is from `hash_combine` function of the Boost library.
 */
template<typename T>
struct hash_matrix : std::unary_function<T, std::size_t> {
  std::size_t operator()(T const& matrix) const {
    size_t seed = 0;
    for (size_t i = 0; i < matrix.size(); ++i) {
      auto elem = *(matrix.data() + i);
      seed ^= std::hash<typename T::Scalar>()(elem) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
    return seed;
  }
};

/**
 * @brief An alternative hash function optimized to reduce collision rate for 
 * numerous vectors that all contain elements from a small continuous distribution.
 */
std::size_t operator()(std::vector<uint32_t> const& vec) const {
  std::size_t seed = vec.size();
  for(auto x : vec) {
    x = ((x >> 16) ^ x) * 0x45d9f3b;
    x = ((x >> 16) ^ x) * 0x45d9f3b;
    x = (x >> 16) ^ x;
    seed ^= x + 0x9e3779b9 + (seed << 6) + (seed >> 2);
  }
  return seed;
}

References: https://stackoverflow.com/questions/20511347/a-good-hash-function-for-a-vector/; https://stackoverflow.com/questions/17016175/c-unordered-map-using-a-custom-class-type-as-the-key; https://wjngkoh.wordpress.com/2015/03/04/c-hash-function-for-eigen-matrix-and-vector/

class inheritance and polymorphism

pcl::Registration<PointT, PointT>::Ptr base_ptr;
if (method == "pcl")
  base_ptr = std::make_shared<pcl::GeneralizedICP<PointT, PointT>>();
else if (method == "pclmt")
  base_ptr = std::make_shared<pcl::MultithreadedGeneralizedICP<PointT, PointT>>();
else if (method == "fast")
  base_ptr = std::make_shared<fast_gicp::FastGICP<PointT, PointT>>();
else
  throw std::runtime_error("unknown gicp method");
// Then use base_ptr to call functions in derived class
// Note: these functions must already exist in base class as virtual functions
base_ptr->setInputSource(source);
base_ptr->setInputTarget(target);

factory method

The most basic factory method

unique_ptr<Animal> makeAnimal(const string& type, int number) {
  if (type == "Dog") return make_unique<Dog>(number);
  if (type == "Cat") return make_unique<Cat>(number);
  throw runtime_error("Invalid type!")
}

Reference: Unforgettable Factory Registration

Last updated