📜 ⬆️ ⬇️

Methods like first class citizens in C ++

The other day, while walking on the bug tracker, gcc came across an interesting bug , it uses several C ++ 11 features at once:


Analyzing this bug, I thought that now you can conveniently implement methods as first class citizens

Actually, Wikipedia explains to us that such first class citizens are an entity that can be created during the program’s work, passed as a parameter, assigned to a variable, can be the result of a function’s operation.
')

Training


Choosing a compiler

Since I didn’t have fresh gcc or msvc on hand, I decided to put together a fresh clang-3.1:
mkdir llvm cd llvm svn co http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_31/final ./ cd tools svn co http://llvm.org/svn/llvm-project/cfe/tags/RELEASE_31/final clang cd ../../ mkdir build cd build cmake ../llvm -DCMAKE_INSTALL_PREFIX=/home/pixel/fakeroot -DCMAKE_BUILD_TYPE=Release make -j4 make check-all make install 


Choosing the libcxx library

I also decided to build the libcxx library to use all the features of the new compiler:
 mkdir libcxx cd libcxx svn co http://llvm.org/svn/llvm-project/libcxx/trunk ./ cd ../ mkdir build_libcxx cd build_libcxx CC=clang CXX=clang++ cmake ../libcxx -DCMAKE_INSTALL_PREFIX=/home/pixel/fakeroot -DCMAKE_BUILD_TYPE=Release make -j4 make install 


A few words about libcxx build: I decided to get the latest version from the trunk, since the last release did not want to be assembled from me (I didn’t want to understand, so I took the trunk). Also libcxx should be built using clang, for this I put the CC and CXX environment variables to replace the compiler with clang. Also, for some reason, I did not want to run tests ( make check-libcxx )

CMakeLists.txt example to use freshly picked clang and libcxx

 cmake_minimum_required(VERSION 2.8) project (clang_haxxs) add_definitions(-std=c++11 -nostdinc++) include_directories(/home/pixel/fakeroot/lib/clang/3.1/include) include_directories(/home/pixel/fakeroot/include/c++/v1) link_directories(/home/pixel/fakeroot/lib) add_executable(clang_haxxs main.cpp) set_target_properties(clang_haxxs PROPERTIES LINK_FLAGS -stdlib=libc++) 


Accordingly, for cmake, we redefine the CC and CXX environment variables in the same way as for libcxx.

Explanatory Example


So, the preparatory process is completed, go to the example:
 #include <iostream> #include <functional> using namespace std; struct FirstClass { FirstClass(): x(0) { } int get_x() const { return x; } function<int ()> f1 = [this]() -> int { cout << "called member function f1..." << endl; ++x; f1 = f2; return 5; }; private: function<int ()> f2 = [this]() -> int { cout << "called member function f2..." << endl; return x; }; int x; }; int main() { FirstClass m; m.f1(); m.f1(); function<int ()> f3 = []() -> int { cout << "called free function f3..." << endl; return 100500; }; m.f1 = f3; m.f1(); return 0; } 

The output of the program:
called member function f1...
called member function f2...
called free function f3...


In fact, similar functionality can be implemented without c ++ 11, but it will look less readable. The main contribution to the readability of the code is made by non-static member initialisation - we get a declaration and method implementation similar to the usual methods in C ++ - 03. The remaining features are more or less emulated by C ++ - 03 tools and third-party libraries: boost :: function, boost :: lambda.

Immersion


Let's take a closer look at what we can do with such objects:
Emulation of static and non-static methods

Everything is simple, the method is not static if it has access to this . Accordingly, when defining a lambda function in the body of a class, we add to the capture list this . Now, from the lambda function, we can apply to all members of the class (including private members).

Here it has a peculiarity: in fact, the concept of static functions is not quite correctly used here, since initially in C ++ they are defined as functions that can be called without a created object, here we still have to create an object in order to reach a function.

Setting methods outside the class

We figured out how to define a non-static function, now it remains to understand how to do it outside the class, it’s very simple - you need to pass the object to which this function is attached to the capture list:
  function<int ()> f4 = [&m]() ->int { cout << "called free function f4 with capture list..." << endl; return m.get_x() + 1; }; m.f1 = f4; m.f1(); 


Here we must be careful when passing the object reference to the capture list, since the operation of defining the function and linking it to the object is separated in time, the following error can be made:
"Snap to the wrong object as specified in the capture list."

Also, one more restriction that is present here, if we attach a function outside the class declaration, then we lose access to the private class variables:
  function<int ()> err = [&m]() ->int { cout << "called free function err with capture list..." << endl; return mx + 1; }; 

In this case, the compiler curses: /usr/src/projects/clang/usage/main.cpp:64:12: error: 'x' is a private member of 'FirstClass'

Do not override a method

Everything is simple here, since the method is an ordinary member of the class, then adding const to its description, we just get what we need:
  struct FirstClassConst { const function <int()> f1 = []() -> int { return 1; }; }; FirstClassConst mc; mc.f1 = f3; 

The compiler scolds us: /usr/src/projects/clang/usage/main.cpp:70:8: error: no viable overloaded '='
mc.f1 = f3;
~~~~~ ^ ~~
/usr/src/projects/clang/usage/main.cpp:70:8: error: no viable overloaded '='
mc.f1 = f3;
~~~~~ ^ ~~


Lack of const methods

Fair C ++ methods have the ability to determine that a method does not change class members, and it can be applied to a constant object, such methods are marked with the const qualifier. In the example, this is the get_x method.
If we implement methods as objects, this possibility disappears; instead, we can change the terms of a constant object:
  struct MutableFirstClass { int x; MutableFirstClass(): x(0){} int nonConstMethod() { ++x; return x; } function <int()> f1 = [this]() -> int { this->x = 100500; return x; }; }; const MutableFirstClass mm; mm.f1(); //mm.nonConstMethod(); 

If you uncomment the last call, the compiler swears as follows: /usr/src/projects/clang/usage/main.cpp:93:2: error: member function 'nonConstMethod' not viable: 'this' argument has type 'const MutableFirstClass', but function is not marked const
mm.nonConstMethod();
^~
/usr/src/projects/clang/usage/main.cpp:93:2: error: member function 'nonConstMethod' not viable: 'this' argument has type 'const MutableFirstClass', but function is not marked const
mm.nonConstMethod();
^~

Most likely, the following sequence of actions occurs:
non static member initialisation is nothing more than syntactic sugar, and therefore this capture in the capture list takes place in the constructor, and in the constructor this has the type MutableFirstClass * const , and therefore we can change the values ​​of variables.

As far as I remember, in constant objects, change the values ​​of members - UB (except for members marked with the mutable qualifier), so you need to carefully use such methods in constant objects.

What's next


In fact, the possibility of using this functionality is quite controversial - on the one hand, we can easily implement the “Decorator” pattern almost as in a python, and this is one of the strengths: we get rid of the tedious implementation of a heap of classes of heirs, as in GoF. We can also decorate each object in an individual way: for example, we can write a decorate function that takes an object as input and adds a decorator to one of the methods. This cannot be done using this pattern as described in GoF.

On the other hand, the lack of protection for members of constant objects is a very serious drawback, so you need to seriously consider before applying this solution.

Also, memory consumption increases, in the implementation of libcxx each such method takes 16 bytes, so with an increase in the number of methods, we will receive more and more bold objects.

You should also take time measurements and compare the speed of calling such methods compared to native C ++ methods (you can compare speed with virtual methods).

Source: https://habr.com/ru/post/145673/


All Articles