На днях, гуляя по багтрекеру gcc наткнулся на интересный баг, в нем используется сразу несколько возможностей C++11:


Анализируя этот баг, я подумал, что теперь можно удобно реализовать методы как first class citizens

Собственно, википедия объясняет нам, что такое first class citizens — это некая сущность, которая может быть создана в процессе работы программы, передана как параметр, присвоена переменной, может быть результатом работы функции.

Подготовка


Выбираем компилятор

Так как под рукой у меня не было свежего gcc или msvc, я решил собрать свежий 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


Выбираем библиотеку libcxx

Также я решил собрать библиотеку libcxx для использования всех возможностей нового компилятора:
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


Несколько слов о сборке libcxx: я решил взять последнюю версию из trunk, так как последний релиз не захотел у меня собираться (разбираться не хотелось, поэтому взял trunk). Также libcxx должна собираться с помощью clang, для этого я ставлю переменные окружения CC и CXX для замены компилятора на clang. Также у меня почему-то не захотели запускаться тесты (make check-libcxx)

Пример CMakeLists.txt для использования свежесобранного clang и 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++)


Соответственно, для cmake переопределяем переменные окружения CC и CXX аналогично, как при сборке libcxx.

Поясняющий пример


Итак, подготовительный процесс закончен, переходим к примеру:
#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;
}

Вывод программы:
called member function f1...
called member function f2...
called free function f3...


На самом деле, аналогичную функциональность можно реализовать и без c++11, но будет это выглядеть менее читабельно. Основной вклад в читабельность кода вносит non-static member initialisation — мы получаем декларацию и реализацию метода аналогичную обычным методам в C++-03. Остальные возможности более-менее эмулируются средствами C++-03 и сторонними библиотеками: boost::function, boost::lambda.

Погружение


Рассмотрим подробнее, что мы можем делать с такими объектами:
Эмуляция статических и нестатических методов

Здесь все просто, метод не является статическим, если он имеет доступ к this. Соответственно, при определении лямбда функции в теле класса, мы добавляем в capture list this. Теперь из лямбда функции мы можем обращаться ко всем членам класса (в том числе и приватным).

Здесь есть она особенность: на самом деле, здесь не совсем корректно используется понятие статических функций, так как изначально в C++ они определяются как функции, которые можно вызывать без созданного объекта, здесь мы все же вынуждены создать объект, чтобы дос��учаться до функции.

Настройка методов вне класса

Как нестатическую определить функцию мы разобрались, теперь осталось понять, как это сделать вне класса, очень просто — необходимо в 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();


Здесь мы должны соблюдать аккуратность при передаче ссылки на объект в capture list, так как операция определения функции и привязки ее к объекту разнесена по времени, можно допустить следующую ошибку:
«Привязать не к тому объекту, который указан в capture list».

Также, еще одно ограничение которое здесь присутствует, если мы прицепляем функцию вне декларации класса, то мы теряем доступ к приватным переменным класса:
	function<int ()> err = [&m]() ->int
	{
		cout << "called free function err with capture list..." << endl;
		return m.x + 1;
	};

При этом компилятор ругается: /usr/src/projects/clang/usage/main.cpp:64:12: error: 'x' is a private member of 'FirstClass'

Запрещение переопределения метода

Здесь все просто, так как метод является обычным членом класса, то добавив const к его описанию, мы как раз получаем то что нужно:
	struct FirstClassConst
	{
		const function <int()> f1 = []() -> int
		{
			return 1;
		};
	};

	FirstClassConst mc;
	mc.f1 = f3;

Компилятор ругает нас: /usr/src/projects/clang/usage/main.cpp:70:8: error: no viable overloaded '='
mc.f1 = f3;
~~~~~ ^ ~~


Отсутствие const методов

У честных C++ методов есть возможность определения, что метод не меняет членов класса, и его можно применять к константному объекту, такие методы помечаются квалификатором const. В примере — это метод get_x.
Если мы реализуем методы, как объекты, то такая возможность пропадает, вместо этого мы можем менять члены у константного объекта:
	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();

Если раскомментировать последний вызов, то компилятор ругается следующим образом:/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();
^~

Скорее всего присходит следующая последовательность действий:
non static member initialisation не более чем синтаксический сахар, и поэтому захват this в capture list происходит в конструкторе, а в конструкторе this имеет тип MutableFirstClass * const, и поэтому мы можем менять значения переменных.

Насколько я помню, в константных объектах менять значения членов — UB (кроме членов, помеченных квалификатором mutable), поэтому необходимо осторожно использовать такие методы в константных объектах.

Что дальше


На самом деле, возможность применения этого функционала довольно спорна — с одной стороны, мы легко можем реализовать паттерн «Декоратор» почти как в питоне, и это одна из сильных сторон: мы избавляемся от утомительной реализации кучи классов наследников, как в GoF. Также мы можем декорировать каждый объект индивидуальным способом: например, мы можем написать функцию decorate, которая получает на вход объект, и добавляет декоратор к одному из методов. Такое невозможно сделать, используя данный паттерн так, как он описан в GoF.

С другой стороны, отсутствие защиты членов константных объектов — это очень серьезный недостаток, поэтому необходимо серьезно подумать перед применением данного решения.

Также возрастает потребление памяти, в имплементации от libcxx каждый такой метод занимает 16 байт, таким образом, с увеличением количества методов, мы будем получать все более жирные объекты.

Также следует провести замеры времени и сравнить скорость вызова таких методов по сравнению с нативными методами C++ (можно сравнить скорость с виртуальными методами).