perjantai 5. tammikuuta 2018

Modern C++

A friend is/was learning C++/opengl stuffs, and was annoyed with all the pointers in C++, I found this odd.. and well, when I threatened to message her in the evening, drunk, something about Modern C++, she suggested I should write a blog post about it.

So, Here I am, drunk, and trying to figure out what to write.

I asked about tips from, fellow coders at IRCNet #OpenGL (yes, I know C++, but I don't know everything about C++, its insane language, (and on the other fact, yes OpenGL is the most awsome graphics api there is)).
All the things here represents my point of view, if someone views the world differently, thats their problem/opportunity to correct my blasphemy.

I think the topics, or points of interests are as follows:
  • Tools
  • Coding conventions
  • collections (stl things, and string)
  • auto
  • enum & using & namespaces
  • nullptr
  • pointers & references, what why when how, and how god awful they really are.
  • lambda
  • initializer lists
  • threads
  • time
  • templates
  • exceptions
  • R-Value / L-Value semantics
  • move semantics
  • Boost libraries
  • (Hell no, I'm staying away from regexp)
  • debugging
  • links

Changes:

14.01.2018:
  • more about pointers
7.01.2018:
  • added more topics
  • update to std::string rant
  • enum & using & namespaces topic and content
  • content for "pointers", "auto", "initializer lists"
  • links added

Tools

So lets start at the tooling, currently it seems to be modern to approach C++ projects with CMake tooling, CMake can be used to generate project solutions for whatever toolchains you are using in the project, this includes xCode, Visual studio, and pretty much any sane IDE out there.

For source version control, git, mercurial.
For graphics debugging RenderDoc, nSight (nVidia). AMD also has its own tools GPU Perfstudio and the likes.
Python is nice to all sorts of scripting and maintanence, if you are doing bigger project, and need a build support scripting.
For IDE, Visual studio and Visual Studio Code, are excellent. Clion was also suggested as IDE, I personally have not used it, so can't really say anything about it.

Coding conventions

There are several coding conventions, mine, is a unique mix of BSD coding convention, and my own view that all code should concentrate on readability and simplicity, if there is a chance that code can cause bugs later on, due to how it was written, then the writing is wrong.
Anyhow, all code should go through a formatter ( http://clang.llvm.org/docs/ClangFormat.html ), choose one way to do everything, and let the machine churn the formatting to "One correct way to rule them all" code form (unfortunately the tool doesnt support BSD coding convertions, so I personally am in the minority). This eases up everything in a team, as all the code in source control are in one way.

Collections

STL-Collections (vector, unordered_set, unordered_map, list, string), are awesome, and everyone should learn them. There really is no reason to make C type allocations anywhere anymore, and these collections should be used to manage all sorts of memory allocations.


For example, allocation a buffer for an image, could be done:

int width = 100;

int height = 100;

int channels = 4; // 4 == RGBA

int bytesPerChannel = 1;

std::vector<uint8_t> buffer;

buffer.resize(width * height * channels * bytesPerChannel);
// and access the buffer with buffer.data();



std::string is also a collection. it is a very special collection, modern applications consider std::string to be utf8 coded strings.

Update: I had a constructive arguments about this on the ircnet, C++ strings are really old, and well storing utf8 in std::string and treating it as just a container (std::vector<char> style), it works, but doesn't really give tools for string parsing and stuff.

Fact is, that unicode parsing in C++ is shiet.. I deal with this with making a rule "std::string is utf8 encoded", and if there is a case where I need to get the rune representation/unicode codepoints, then Ill convert the string to full 32bit unicode points (funny fact about utf32, its fixed length!, once we get more runes that 32bit can store, utf32 is f*ckd).

Oh another thing, not a very important, but currently, Ill do all sorts of parsings with fmt libary, if I have a text of string, where I want to replace some part with another string, Ill use fmt to generete a string, like: auto str = fmt::format("Hey {0} this works", "cutie");

enum & using & namespaces

C++11 brought enum class, and using keywords, to help the sanity with namespaces in C++. C++ lacks modules or any sane kind of way to manage compilation units, source code etc. we just have a "raw" include thingy, that basically just copy pastes source code from one file into this file. The preprocessor does some smart things with defines and macros (not much really, it cant handle strings and comparisons, c preprocessor is poor mans programming language.. that some people take to bordering insanity.).
enum class foo; lets you define strongly typed enumerations, these are almost god sent, capsulating enumerations inside  the entity that defines the enumeration.
using keyword allows us to finally respect namespaces with aliases, previously all we had were typenames and #defines, now we have namespace respecting { using UTF8 = uint8_t; } kind of construct that seems readable.

auto

auto keyword was added in C++11, it allows you to forget, and write very maintainable code, by omitting the information about what kind of type is being handled. Auto lets the coder to ignore the type, and let the compiler make the decision of what the type is, using auto, as much as possible, lets coder to change types, around the code, much more dynamically (less refactoring), and often makes the code less verbose.
on the flipside, auto can make a codebase, daunting to read afterwards, as type is deducted at compile times, this can potentially make very simple code snippet, to be endless rabbit hole. (to find what the type really is, in the worst case, compile the code, and set a breakpoint there, and let the IDE tell you the type).

nullptr

nullptr is a C++11 addition, it allows the world to get rid of nonstandard NULL define. Often, if the codebase is riddled with NULL (or functions with void as empty argument in [int foo(void)]), it is a sign that a) codebase is old or b) whoever wrote it, hasn't stayed up to date with times.

pointers

Okay, C++11 brought lots of things to pointers, smart pointers, and nullptr type.

The shared_ptr is a reference counting pointer type, these pointers share a counter and a resource (the counter is thread safe, the resource is not), if the counter hits 0, the resource is deleted. weak_ptr is a partner for sharer_ptr, it is used to describe a non ownership relation to a resource, to use it, you have to convert it back to shared_ptr pointer.

unique_ptr owns the resource, once it gets killed, the unique_ptr deletes the resouce.

more often than pointers, references are used, everywhere possible, with containers, these things are immensely powerful.

void consume(char *data, size_t len) { ... }
char *content = malloc(1024);
readFile(myFile, content);
consume(content);
free(content);

// rather than do mallocs and frees or news and delete[]s, use raii
// and let vector do all that stuff:
void consume(const std::vector<char>& data) { ... } // we get length from vector

std::vector<char> content;
content.resize(1024);
readFile(myFile, content.data()); // not sure, might be actually &content[0]
consume(content);

with references, you could make assumption that references, cannot be nullptr (a sane assumption, I have not seen a codebase where this assumption has not been made).
also with references, it sort of can say to other programmers, what parameters are "in" and what are "out".. for example:

bool toInt(const std::string& str, int& value);

in that example, coder can see, that immutable string is the "input" parameter to the function, and "value" is out parameter. The example is a bit artificial in sense, that it returns bool, as indication did the transformation succeed (it is possible to code in this style), many coders, would prefer that function to return a int, and have it take bool& success, in. But this is entirely about what flavor you yourself prefer. Also in the example, if the codebase allows exceptions (historically, exceptions have been frowned upon, and if a library uses exceptions, it cannot be used on projects, that do not support RTTI or exceptions).
References, are easy to be thought of as "just pointers", but, unfortunately, they have some additionaly magic placed into them by C++, something that transforms non valua objects into real value objects, and some kind of magic called "&&" or "&&&&&" .. ( like auto&&&& foo; ).

Codebases, for example UI library or something, are totally possible to architect, without pointers any/every/where, with references and standard template library, by using std::map, std::list and std::vector (though, you have to know those containers, their allocation behavior, to a degree).

lambda

Lambda functions is the greatest thing since invention of bread and butter. They are bit complicated, with all the rules that comes with them in C++, its not like in C# or Java, where you can throw things around and just YOLO your way around. In C++ you can assign lambda functions to C function pointers (following certain rules, not always), and into std::function class (following certain rules). But once you understand the rules, lamdas lets you create all sorts of callback style programmings.

initializer lists

I'm not sure what should I say about these.. In C++ you can initialize stuff in constructors, using initializer lists:
asdasd
class Foo
{
private:
  int faa;
public:
  Foo() {}
};
/// options to initialize faa are
// a) in place (in header file, much preferred, as if you have multiple ctor, the
//    default value is set to 0 automatically)
int faa = 0; 
// b) initializer list in Foo ctor
Foo() : faa(0) {}
// c) default value in ctor parameter
Foo(int faa = 0) : faa(faa) {}

/// Also one other thing would be to use the new '{}' initializers
int faa{0};
Foo() : faa{0} {}
foo(int faa = 0) : faa{faa} {}

at the moment I am trying to start embracing the {} braket initializers everywhere to distinguish calls from function calls.

threads

C++11, brought threads, yey, finally. TODO

time

TODO

templates

TODO

exceptions

TODO

R-Value / L-Value semantics

TODO

move semantics

TODO

Boost libraries

Boost libraries seemed like a good idea at one time, when C++ did not see a release of specs every few years. At that time the problems usually were boost library incompatibility between versions, and different versions used by 3rd party libraries. I doubt this problem has gone away, I've lived my life as far from boost as possible, the code in boost libraries is usually deep template magic, and interconnections between different parts of the library. If you take boost in, your codebase will be married to it, for life. Boost contains solution for everything, polygons and maths? yes, build system? yes, reflection? probably, extended filesystem? yes, networking, yes.
I would avoid it at all cost.
Too much dependencies, with possibly deep dependencies to other parts of the framework. The alternative is to use many small libraries, and just get things done.

(Hell no, I'm staying away from regexp)

in life of a developer, regexp comes before you as a "good idea" every 1-2 years.. usually people deal with it, by relearning it each time. This is also the strategy I am using.
Regexp was added to C++XX, Im not sure, but maybe in C++11. I've used it 1 time, a month ago, on a libarary that was tossed out of the product, I am not suprised.

debugging

TODO

Links

  • cppreference.com pretty must up to date documentation on standard C/C++ (do not trust microsoft or any other specific vendors, they will screw you over).
  • github.com/fffaraz/awesome-cpp list of C++ libraries (not all, but good list of tools)
  • code.visualstudio.com "ide" for free from microsoft, requires a lot of tinkering to get it to compile whatever you want, cross platform!
  • visualstudio.com ide for "free" from microsoft, requires a little to get used to. windows only (dont be fooled by vs for mac, it is not visual studio).
  • cmake.org cmake tools for project generation (should be used to handle all library and stuff linking)