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
}
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
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
};
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>());
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.
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
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()
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
}
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> { ... };
...
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];
};
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.
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;
}
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
}
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 volatiledoes 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;
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;
}
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!")
}