DEV Community

Cover image for How to Write a C++ Library
Parth Agarwal
Parth Agarwal

Posted on • Edited on • Originally published at ra101.hashnode.dev

How to Write a C++ Library

Prerequisite: One must be familiar with coding in C++.

I coded my first C++ libraries 5 years back, I had just started college, and wanted to do something solid!, One fine evening, I was looking back at that code, It was so, poorly written, So I started fixing it. I googled C++ Convention, After many search results, I found out there are no good resources out there. So I took upon myself, To Write One!

These are coding practices one may follow to write readable good code.


File Types:

Any programing file, in the end, is nothing but a text file with a fancy extension. In the Book of Genesis, the mentioned naming style is:

  • .c for C
  • .cpp for C++
  • .h for headers (independent of C or C++)

But due to the opensource nature of GNU, A bunch of different assembler and compilers were made which came with their own extensions variations, such as, .cc, .C, .cxx, .c++ and .hh, .H, .hxx, .hpp, .h++.

TBH, Just chose one set of extensions, after all, even .txt can be compiled.

I use, (Since Most IDE recognize them):

  • .c for C
  • .cpp for C++
  • .h for C headers
  • .hpp for C++ headers

makefile:

A makefile is a special file, containing shell commands, that you create and name makefile (no extensions)

It is executed using the make command. My Typical makefile looks like this:

# Variables
CXX=g++
CXXFLAGS=-std=c++11 -w

# <command> : <dependencies>

# would have worked with main.cpp as well, added rest of
# dependencies to add a check before compiler returns an error.
build: main.cpp local_header.hpp includes/external_lib.hpp 
    @echo "\n\nmain.cpp: Building..."
    $(CXX) $(CXXFLAGS) -o main.out main.cpp

run: main.out
    @echo "\n\nmain.cpp: Executing..."
    ./main.out

clean: main.out
    @echo "\n\nmain.cpp: Cleaning..."
    rm main.out

# test command is for show, I have probably never used it πŸ˜…πŸ˜‚ 
test: test.cpp
    @echo "\n\ntest.cpp: Building..."
    $(CXX) $(CXXFLAGS) -o test.out test.cpp
    @echo "\n\ntest.cpp: Executing..."
    ./test.out
    @echo "\n\ntest.cpp: Cleaning..."
    rm ./test.out

all: build run clean
Enter fullscreen mode Exit fullscreen mode
$ # To build, run and clean the main.cpp
$ make all
Enter fullscreen mode Exit fullscreen mode

@pauljlucas suggested:

You really should use something like GNU Automake or CMake instead of hand-written makefiles.

These tools may add overhead of other files, but these files are informative files. Use these tools, if they benefit your project.

NOTE: use -MMD flag to get a .d file, containing all dependencies.


PreProcessors:

The preprocessors are the directives, which instruct the compiler to preprocess the information before starting compilation. All preprocessors start with #, For example: #include<iosteam>

These are the coding practices, I use:

  • Double Quotes ("...") for including local headers, This rule increases readability of Code

  • 3 Levels of includes (I picked this habit while working in python) with line-space between them.

    • First, for core g++ system files.
    • Second, for externally added libraries.
    • Third, for locally created files.

@pauljlucas suggested:

You should include local headers first β€” then system headers. The reason is to guarantee that all local headers are self-sufficient

By "self-sufficient", Xe means, local headers are not depending on System or External libraries of this file.
This although is a good way of checking, I believe this "check" is not something headers/compilers should do, well at least not at the file level.
In an ideal world, Each header would have its own test files to resolve this, until then, going against the natural order might not be that bad.

  • #pragma one for header files

    • What #include does is that it adds the mentioned file at the top of the code (you can check it by using --save-temp flag during compiling) So, If a header file is called again somewhere else, The Whole Code will be compiled again. Since Header files don't typically initiate a variable. It is Advised to compile it once for performance enhancement.
    • One can use #indef ... #include ... #endif, but it is tedious, so#pragma once reduces possibilities for bugs due to manual mishandling.
  • DO NOT USE using namespace ... in header file

    • It may lead to a re-declaration error.
    • It may populate the header file with its function

SO, According to the above rules, A good header would be.

#pragma once // 2 line-space afterwards


#include<system_file_1>
#include<system_file_2> // 1 line-space afterwards

#include<external_lib1_1> // 1 line-space afterwards
#include<external_lib2_2> // club external libs together
#include<external_lib2_3> // 1 line-space afterwards

#include "local_file" // 2 line-space afterwards


// using namespace ... DO NOT USE IN HEADERS

namespace ra {
...
}
Enter fullscreen mode Exit fullscreen mode

Nomenclature:

File | namespace | Class | function:

  • Nothing is fixed, one may use camelCase, PascalCase or snake_case.
  • I personally find snake_case to be more readable.

Template Parameter:

  • same as above, no rules.
  • I prefer PascalCase here.

Constants | Macros:

  • UPPER_SNAKE_CASE is preferred everywhere.

Variables:

  • Before IDE(s) were a thing, People just couldn't hover over variables to get info. So some old libraries use the following conventions:
    • m_<any case> for private members:
    • t_<any case> for function parameters
  • I personally, discard this rule.

Taboo of Naming:

Check this link


Comments:

C++ allows for 2 types of comments:

// This is single line Comment
Enter fullscreen mode Exit fullscreen mode
/*
...
This is Multi-Line Comment
...
*/
Enter fullscreen mode Exit fullscreen mode

Generally speaking, single-line comments provide more control over the appearance and are considered better.

As a python developer, I wholeheartedly disagree, This is how I prefer my code!


class custom_class {
    /*
        A definition of what does this class represent
        - Preferred Usage:
            ...
    */
    private:
        // How this variable affects / Where this may be used
        float variable {3.14};

        // What does this function do
        void function();
}

void custom_class::function(){
    /*
        A definition of what does this function do
        - Input: ...
        - Output: ...
    */
}

Enter fullscreen mode Exit fullscreen mode

Custom Exceptions:

Custom exceptions provide you the flexibility to add attributes and methods that are not part of a standard programming language. These are most helpful in Debugging when code is unable to compile.

Below is My Favorite Exception:

// Code

namespace ra
{
    class external_exception : public std::logic_error
    {
    public:
        external_exception(
            std::string function = __builtin_FUNCTION()
        ) : std::logic_error("`" + function + "` called for external exception!"){};
    };
}

Enter fullscreen mode Exit fullscreen mode
// Usage

void main (){ throw ra::external_exception(); }
Enter fullscreen mode Exit fullscreen mode
# Output

terminate called after throwing an instance of 'ra::external_exception'
  what():  `main` called for external exception!
Enter fullscreen mode Exit fullscreen mode

Let me explain, In argument of constructor of exception it says:

std::string function = __builtin_FUNCTION()
Enter fullscreen mode Exit fullscreen mode

__builtin_FUNCTION() return the source location, when called.

Since called in Argument, whenever it is initiated that is the source location, Essentially Making it a traceable Exception

Here We have inherited from logic_error, but other exceptions/errors could be found here

NOTE: In C++ Exceptions and Errors are the same, but in theory, Exceptions are something that could be handled, whereas errors should not be as they break the system.


Modularity

A Modular design is shown to improve the design process by allowing better re-usability, relatively low maintenance, workload handling, and easier debugging processes. Modularity in C++ can be achieved by the following

Template

As defined here : A C++ template is a powerful feature added to C++. It allows you to define the generic classes and generic functions and thus provides support for generic programming. Generic programming is a technique where generic types are used as parameters in algorithms so that they can work for a variety of data types

Templates can be represented in two ways:

template <typename TemplateKlass>
TemplateKlass get_maximum(TemplateKlass x, TemplateKlass y){
   return (x > y)? x: y;
}
Enter fullscreen mode Exit fullscreen mode
template <typename TemplateKlass>
class custom_class {
private:
    TemplateKlass x, y;
public:
    custom_class(TemplateKlass, TemplateKlass);
};
Enter fullscreen mode Exit fullscreen mode

I am will not into details, As there are far better resources

Function as Argument:

We can pass a function in the argument of another function, Well Actually A function pointer is passed to be exact. This comes in handy in the situation, where you have multiple services for the same request, for example, Hashing, you can use so many third-party algorithms for hashing.

bool verify_hash(std::string (*hash_func)(std::string)){
    /*
        This will take in any function with
        input and output as a string 
    */
    return hashed_output == hash_func(input)
}
Enter fullscreen mode Exit fullscreen mode

Inheritance

I picked up this in my Unity3D days, In Unity3D Almost everything is inherited and then used.

The plan is simple, Create a class with a bunch of public members all throwing error.

And Then Everyone would have to Inherit that class and override it.

For Example

class base_class{
    /*
        Everyone will inherit from this class
    */
public:
    base_class(){}
    base_class(float) {throw not_implemented_exception();}

    virtual operator string() const = 0;

    bool operator<(const base_class &other) const { throw not_implemented_exception(); }

    template <typename TemplateKlass>
    void some_func(TemplateKlass temp) {throw not_implemented_exception();}
}
Enter fullscreen mode Exit fullscreen mode
class extended_class : public base_class{
    /*
        This is how Inheritance would work
    */
private:
    float m_temp;
public:
    extended_class(){}
    extended_class(float t_temp) : m_temp(t_temp)

    operator std::string() {return to_string(t_temp);}

    bool operator<(const extended_class &other) const { m_temp < other.m_temp }

    template <typename TemplateKlass>
    void some_func(TemplateKlass temp) {...}
}
Enter fullscreen mode Exit fullscreen mode

Bonus: Icon Pack!

Yeah... I created my own icons too...

GNU Extension Preview Download
.c c.png c.ico
.cpp cpp.png cpp.ico
.cs cs.png cs.ico
.h / .hpp h.png h.ico

If you think I have missed something or you have other coding style or some advice, do let me know, I might append to this post. Make it one central point of all information.

Top comments (8)

Collapse
 
pauljlucas profile image
Paul J. Lucas

You really should use something like GNU Automake or CMake instead of hand-written makefiles.

You should include local headers first β€” then system headers. The reason is to guarantee that all local headers are self-sufficient.

Your code of:

base_class(){};
Enter fullscreen mode Exit fullscreen mode

is incorrect: functions do not have ; after the }.

Collapse
 
ra101 profile image
Parth Agarwal

Thanks for your advice, I have updated the post, with some commentary.

Collapse
 
pauljlucas profile image
Paul J. Lucas
extended_class(){};
Enter fullscreen mode Exit fullscreen mode

is still wrong. Hint: change all occurrences of }; to } for functions.

Thread Thread
 
ra101 profile image
Parth Agarwal

Man! What sort of eagle view, do you have> πŸ˜‚

Thread Thread
 
pauljlucas profile image
Paul J. Lucas

I review a lot of code.

Collapse
 
pauljlucas profile image
Paul J. Lucas

By "self-sufficient", Xe means, local headers are not depending on System or External libraries.

That's not what I meant. They can depend on system of external libraries; but, if they do, they #include them themselves rather that rely on the file they're being included into.

If I have this:

// foo.h
class C {
    // ...
    std::string _s;
};
Enter fullscreen mode Exit fullscreen mode

and:

// bar.h
#include <string>
#include "foo.h"

void f( C const& );
Enter fullscreen mode Exit fullscreen mode

then this works by "accident." Even though foo.h uses std::string and foo.h does not #include <string>, it works because <string> is included by bar.h before it includes foo.h. However, if you change the includes in bar.h, you could break foo.h. So it foo.h wants std::string, it needs to #include <string> itself.

Collapse
 
ra101 profile image
Parth Agarwal • Edited

That is exactly, What i meant, Probably not worded correct tho...

local headers are not depending on System or External libraries. (of this file)

Thanks a lot tho,

Collapse
 
pgradot profile image
Pierre Gradot • Edited

TBH, Just chose one set of extensions, after all, even .txt can be compiled.

Well... No :

$ gcc main.txt
C:/[...]/x86_64-w64-mingw32/bin/ld.exe:main.txt: file format not recognized; treating as linker script
Enter fullscreen mode Exit fullscreen mode

makefile:

As said by other people, try CMake ;)

#pragma one for header files

I guess you wanted to say #pragma once (as in your code snippet below)

DO NOT USE using namespace ... in header file

... and be very carefule in *.cpp files, you are making your code ready for future nameclashes.

Below is My Favorite Exception:

For me, there is one problem with this class: it doesn't have any real semantic. The user has absolutely no idea what the actual problem was.

The plan is simple, Create a class with a bunch of public members all throwing error.

And Then Everyone would have to Inherit that class and override it.

Wait what!? Please: use pure virtual functions! Also, the purpose of the class itself, as well as its actual content, is very questionnable.