/** Правило минимального изменения кода: Если нет предпочтений к оформлению какой-то многострочной конструкции в коде, то следует писать так, чтобы при дальнейшем возможном изменении кода, количество изменяемых строк было минимально. Это упрощает жизнь при работе с системой контроля версии и анализе изменений. Примеры: Конструктор класса нужно оформлять так: */ object() : m_active(false) , m_name("object") , m_value(0) { } /** При добавлении нового члена класса в список инициализации просто появляется новая строка, другие не изменяются. У enum после последнего значения нужно ставить запятую: */ enum type_id { type_id_one, type_id_two, }; /** При добавлении нового значения или удалении изменится только одна строка. Не следует выравнивать аргументы функции по открывающей скобке. так как если длина имени функции изменится, то придётся выравнивать и аргументы, что приведёт к лишим изменениям кода. */ /// Плохо: void myfunction(int a1, int a2, int a3); /// Хорошо: void myfunction( int a1, int a2, int a3); /// И так далее.
C++ for kids
Субъективные зарисовки на тему "Как я программировал летом".
среда, 21 августа 2013 г.
minimum rule
shared_ptr factory
/** Многие до сих пор пишут свои фабрики объектов, хотя есть boost::shared_ptr и boost::factory При чём есть возможность передавать аргументы при создании объектов. Например, есть система полиморфных объектов: */ #include <map> #include <iostream> #include <boost/shared_ptr.hpp> #include <boost/bind.hpp> #include <boost/assign.hpp> #include <boost/function.hpp> #include <boost/functional/factory.hpp> #define LOG(WHAT) std::cout << WHAT << std::endl struct A { virtual void func() = 0; protected: ~A() {} }; struct B: A { B(int a): a(a) {} void func() { LOG("it's B"); } int a; }; struct C: A { C(int a): a(a) {} void func() { LOG("it's C"); } int a; }; /** Объекты при создании принимают один аргумент: int. Давайте создадим фабрику, которая будет создавать объекты, помещённые в смарт поинтер boost::shared_ptr. Для создания фабрики достаточно описать следующее: */ typedef boost::function<boost::shared_ptr<A>(int)> factory_t; /** Связываем фабрики конкретных типов "B" и "C" с идентификаторами: "class_B" и "class_C": */ std::map<std::string, factory_t> fact = boost::assign::map_list_of ("class_B", factory_t(boost::factory<boost::shared_ptr<B> >())) ("class_C", factory_t(boost::factory<boost::shared_ptr<C> >())) ; /** Использование: Создаём объект по идентификатору "class_B" и передаём аргумент, например, 33. */ boost::shared_ptr<A> a = fact["class_B"](33); a->func(); /** Можно добавлять "фабрики по умолчанию", более подробно в доках http://www.boost.org/doc/libs/1_51_0/libs/functional/factory/doc/html/index.html */
template-template parameters
/** Шаблонные шаблонные параметры это прошлый век, они не удобны. Я постараюсь объяснить как их избежать. Есть класс A, в котором нужно объявлять контейнер из произвольного типа, а тип контейнера передавать параметром шаблона: */ template <typename T, typename Container> struct A { typedef Container<T> type; }; /** Но так как Container это шаблон, принимающий два параметра: тип данных и тип аллокатора, то следует писать так: */ template <typename T, template<typename, typename> class Container> struct A { typedef Container<T, std::allocator<T> > type; } /** Это плохо, я не хочу указывать явно и самостоятельно тип аллокатора, это может првести к ошибке. Ещё одна из причин, почему шаблонные шаблонные параметры неудобны. Чтобы получить тип теперь мы должны вызвать: */ typedef A<int, std::vector>::type type; /** В boost mpl есть плейсхолдеры, которые используются для создания лямбда метафункций. Так вот, с помощью них можно избежать создания шаблонных шаблонных параметров. То есть я передаю в шаблон A уже инстанцированный шаблон так: */ typedef A<int, std::vector<boost::mpl::_1> >::type type; /** Тогда в шаблоне A инстанцирование конейнера с произвольным типом будет выглядеть так: */ #include <boost/mpl/apply.hpp> template <typename T, typename Container> struct A { typedef typename boost::mpl::apply<Container, T>::type type; };
macros-function
/** В #gcc есть возможность писать макросы, возвращающие значения: */ #define STR(WHAT) ({ std::stringstream e; e << WHAT; e.str(); }) /** Этот макрос получает строку, например: STR(33 << 44) получит строку "3344". В Visual Studio это было сделать нельзя. Благодаря стандарту c++11 сейчас можно написать макрос, который будет делать это же: */ #define STR(WHAT) ([]() -> std::string { std::stringstream e; e << WHAT; return e.str(); }()) /** Но с появлением variadic templates в c++11 вряд ли это уже будет нужно. */
shared descriptor
/** Автоматическое закрытие файла с подсчётом ссылок. */ /** Полезно отдавать дескрипторы во владение объектам, которые при уничтожении закрывают этот дескриптор (RAII). Чаще это делается так: */ struct holder { holder(int fd): m_fd(fd) {} ~holder() { ::close(fd); } private: const int m_fd; }; /** Минус данного подхода - нет счётчика ссылок. Для объектов типа FILE* можно было бы использовать так: */ boost::shared_ptr<FILE> file(fopen(), fclose_check); /** Вторым аргументом указывается функтор, удаляющий объект, который проверяет, что входной аргумент не NULL, иначе SIGSEGV */ /** Но что делать для типов не являющихся указателями? Есть решение: */ template <typename T> struct shared_descriptor: public boost::shared_ptr<T> { template <typename D> shared_descriptor(T value, D deleter) : boost::shared_ptr<T>(new T(value), boost::bind(deleter, value)) , m_value(get()) {} private: boost::shared_ptr<T> m_value; }; /** Пример: */ boost::shared_ptr<int> file1 = shared_descriptor<int>(open("file.txt"), close); std::cout << "descriptor: " << *file << std::endl;
array size
/** Как определить длину массива в C++? */ /** Определять длину массива как sizeof(arr)/sizeof(*arr) не совсем безопасно, так как arr может быть обычным указателем. В Google Chrome используется для этого такой макрос: */ template <typename T, size_t N> char (&ArraySizeHelper(T (&array)[N]))[N]; #define ARRAY_SIZE(array) (sizeof(ArraySizeHelper(array))) /** Компилятор ругнётся, если попытаться подставить обычный указатель. Для простоты и лёгкости понимания определения функции ArraySizeHelper, возвращающей ссылку на массив char[N] можно переписать так: */ #include <boost/type_traits/add_reference.hpp> template <typename T, size_t N> typename boost::add_reference<char[N]>::type ArraySizeHelper( T (&)[N] ) ; #define ARRAY_SIZE(array) (sizeof(ArraySizeHelper(array))) /** Почитать подробнее: http://easy-coding.blogspot.com/2011/05/blog-post_24.html http://stackoverflow.com/questions/6376000/how-does-this-array-size-template-work */
logger extension
/** Продолжение предыдущих постов про logger */ /* Давайте не много порассуждаем: зачем макросы? что значит легко используем? что значит расширяем? а эффективен ли?*/ /** Макросы. В данном случае они действительно необходимы. Во-первых, это компактно выглядит. Хотя вы можете сказать, что сложного сделать так? */ typedef logger<std::ostream> std_logger; std_logger mylogger() { return std_logger(std::cout); } /** и использовать так: */ mylogger().get() << "Hello, world!" << 123; /** Вы правы. Но в макросах у нас идёт ещё и проверка уровня лога. Проверку уровня лога нельзя выносить в функцию, так как понадобится излишнее конструирование сообщения, а если это DEBUG-сообщение, а уровень выводимых сообщений ERROR, то будет происходить лишние вычисления сообщения. Если условие ложное, то тело условия не вычисляется. Ещё один аргумент в пользу макросов - возможность указать местоположение в коде с помощью _FILE_ и _LINE_ и т. д. */ /** Легко используем. За счёт использования шаблона, можно подставить не только std::ostream, но и вообще любой другой класс, поддерживающий операцию "<<", даже boost::archive::text_oarchive. */ /** Легко расширяем. Допустим понадобилось синхронизовать сообщения между различными тредами в файл. Делается просто с помощью boost::mutex. Создадим тредобезопачный логгер: */ template <typename Out, typename Mutex> struct logger_safe: logger<Out> { logger_safe(Out& out, Mutex& mutex) : logger<Out>(out) , m_locker(mutex) {} private: boost::unique_lock<Mutex> m_locker; }; /** При создании он будет лочить мьютекс, который ему подаётся. Использование: */ typedef logger_safe<std::ostream, boost::mutex> std_logger_safe; boost::mutex mutex; std_logger_safe(std::cout, mutex).get() << "Hello, world! Safely!"; /** То есть мютекс лочится на время существования объекта, когда мы вводим цельную строку и разлочивается автоматически после окончания ввода */ /** Эффективность. Обычно логгеры формируют строку, чаще std::string, которую ещё надо сформировать. Этот логгер обращается непосредственно к объекту вывода и не требует промежуточной временной строки, это быстро и очень удобно. */ /** Заключение. Я не говорю, что этот логгер подходит для любого проекта, но на основе него и его принципов можно написать гораздо более мощные логгеры. Позже я покажу как выводить в несколько файлов/потоков одновременно, учитывая уровень сообщений. */
Подписаться на:
Сообщения (Atom)