C++ IMP guidelines

Here at IMP we follow these simple C++ guidelines. Feel free to use them!

  1. No “delete” or “new” (even less ‘malloc’s or ‘free’s)
  2. No pointers passed as arguments (and limit usage in functions too)
    // NO
    Type * function(Type * arg);
  3. Arguments should be passed to functions in one of the following ways:
    OutputType function(const InputType1 & input1,
                        const InputType1 & input2,
                        int integerInput, double floatingInput);
     
     
    struct MoreComplexOutput {
        int a;
        vector b;
        ...
    };
    MoreComplexOutput function(const InputType1 & input1, const InputType1 & input2,
                               int integerInput, double floatingInput);
     
     
    std::tuple<int, double, std::string> function(const InputType1 & input1,
                                                  const InputType1 & input2,
                                                  int integerInput, double floatingInput);
     
     
    // When allocation time is a concern this is also valid
    void function(const InputType1 & input1, const InputType1 & input2,
                  OutputType & output,
                  int integerInput, double floatingInput);
     
    // Inplace operations are good too for speed
    void function(InputOutputType1 & inputOutput,
                  int integerInput, double floatingInput);
  4. Prefer std::vector as the standard container to be used. Use std::map as an associative container
  5. Prefer new style loops to indexed loops:
    vector vec = {1, 2, 3};
     
    for(auto & el : vec) {
        cout << el << endl;
    }
  6. Prefer standard library functions (std <algorithm>, <chrono>, … ) to hand crafted or stackoverflow ones. should be used too
    std::sort(std::begin(vector), std::end(vector));
  7. Avoid global state and global variables (if possible)
    // BAD:
    int a;
     
    void do_stuff() {
        a++;
    }
     
    //GOOD:
    int do_stuff(int a) {
        return a + 1;
    }
  8. Use standard exceptions as the standard error control flow tool
    – a function can’t fulfill its contract

    Image readimage(string path) }
        if(!path-&gt;valid())
            throw std::runtime_error("File " + path + " not found");
        return api::read(path);
    }
    // In this case the function couldn't return a valid read image so it trown an exception

    Exceptions should only express exceptional runtime behaviour, not normal workflow cases (they cost time). Throw exceptions, do not catch them.

  9. Use “struct” with objects wich members can vary freely:
    struct Point2d {
        double x;
        double y;
    };
  10. Use “class” for objects that need to hide internal implementation, or handle some private resource:
    class GenericPoint{
    public:
        std::tuple&lt;double, double&gt; getAsPolar() {
            return std::make_tuple(std::hypot(x,y), std::atan2(y, x));
        }
        std::tuple&lt;double, double&gt; getAsCartesian() {
            return std::make_tuple(x, y);
        }
    private:
        double x, y;
    };
     
    class MyFile{
    public:
        MyFile(string path) {
            auto err = api::openFile(handler);
            if(err &lt; 0)
                throw std::runtime_error("Couldn't open file " + path);
        }
        ~MyFile() {
            api::closeFile(handler);
        }
        void write(std::string text) {
            api::writeToOpenFile(handler, text);
        }
    private:
        FILE * handler;
    };
  11. “Get”ter and “Set”ter methods usage should be limited.
    // BAD usage (Java style)
    class Point2d {
    public:
        double getX();
        double getY();
        void setX(double);
        void setY(double);
    private:
        double x;
        double y;
    };
    // This just makes code more complicated and error prone (possibly slower too).
     
     
    // Good usage
    class FileHandler {
    public:
        read();
        write();
        FILE * getNative() {return handler;}
     
    private:
        // FILE is windows only! Don't use it in your real code :-)
        FILE * handler;
    };
    // This just returns a handler to a native resource that could be useful to other users, beside what you have coded in "read()" and "write()"
    // This way users can use the native handler to extend functionality
  12. Usually you just need simple C++ types. Than just go with regular objects and containers (and their arithmetic):
    struct Point {
        Point(double x, double y) { this.x = x; this.y = y; }
     
        double hypot() { return std::hypot(x, y); }
     
        // this may be a way of comparing points (not the only one!)
        bool operator&lt;(Point first, Point second) {return first.hypot() &lt; second.hypot();}
     
     
        double x = 0;
        double y = 0;
    };
    Point operator+(Point first, Point second) {return Point(first.x + second.x, first.y + second.y);}
     
    std::vector simple_function() {
        Point a1;
        a1.x = 2;
        Point a2;
        a2.y = 3;
     
        std::vector vec;
        vec.push_back(a1);
        vec.push_back(a2);
     
        return vec;
    }
    std::vector square_vec_of_points(const std::vector &amp; vec_in) {
        std::vector ret = vec_in;
        for(auto &amp; el : ret) {
            el.x *= el.x;
            el.y *= el.y;
        }
        return ret;
    }
    int main() {
        auto my_vec = simple_function();
        auto squared = square_vec_of_points(vec);
     
        squared.push_back(Point(3, 2));
     
        Point sum = Point(3, 2) + Point(5, 1);
        if(sum &lt; squared.front()) {
            // do something special
        }
    }
  13. Prefer composition to extend an object functionality:
    struct A{};
    class B {
    public:
        void doSomething() {
            ...
            object.doThat();
            ...
        }
    private:
        A object;
    };
     
    // Limit inherithance for what it's really needed.
  14. Sometimes you may need to instantiate objects of different types during program execution, each exposing the same interface. This is called runtime polymorphism.
    If that’s the case go with inherithance and std::unique_ptr or std::shared_ptr.
    Still remember that runtime polymorphism should not be used as a default, since it’s more complicated and costs CPU time

    #include 
    #include 
    #include 
     
    struct A {
        // pure virtual function (interface)
        virtual void fun() = 0;
        virtual ~A() {};
    };
    struct B: public A {
        virtual void fun() override { std::cout &lt;&lt; "I'm a B inside!" &lt;&lt; std::endl; };
        virtual ~B() {};
    };
    struct C: public A {
        virtual void fun() override { std::cout &lt;&lt; "I'm a C inside!" &lt;&lt; std::endl; };
        virtual ~C() {};
    };
     
    int main() {
        std::unique_ptr<a> a;
        // rand is bad. Prefer C++11 
        srand(time(nullptr));
        if(rand() % 2 == 0)
            a = std::make_unique<b>();
        else
            a = std::make_unique();
        a-&gt;fun();
    }
    </b></a>
  15. No “using namespace” in header files.
    Everyone which will use your header will also get all the “using namespace” definition.
    This could cause name clashes and bugs.

    // Example "Header.h"
    using namespace cv;
    ...
     
    // Example "main.cpp"
    #include "Header.h"
     
    struct Point {
    };
     
    int main() {
        Point p;    // CLASH! Is this my ::Point or cv::Point ???
    }
  16. No C-style casts. Limit other type casts too
    // BAD:
    Object * pippo = (Object *) pluto;
    // Limit usage
    Object * pippo = static_cast<object width="300" height="150">(pluto);
     
     
     	<li>
    Avoid unsigned numbers
    Unsigned number cause very hard to spot bugs, while giving very limited advantages.
    Just use signed numbers (char, int, int64_t and so on).
     
    Use caution with functions returning / accepting unsigned numbers.
    <pre lang="c">
    std::vector<int> vec;
    for(int i = 0; i &lt; vec.size() - 1; ++i) { // PROBABLY an UNDERFLOW bug. vector::size() returns an unsigned.
        // ...
    }
    </int>

    Use unsigned numbers only when working with low level stuff (like bits, raw input outputs…), and not for numerical operations (subtraction, division).