Библиография:
[1] Язык программирования C++ специальное издание, Бьерн Страуструп
0. Работа с new/delete
http://www.stroustrup.com/bs_faq2.html#placement-delete --
Placement new and delete (with pseudo-destructor) and other interesting things.
Судя по экспериментам pseudo-destructor поддерживает виртуальность
Эксперимент с вызовом псевдо-деструктора:
#include <stdio.h>
#include <utility>
class A {
public:
A() {printf("A()" "\n");}
virtual ~A() {printf("~A()" "\n");}
};
class B : public A {
public:
B() {printf("B()" "\n");}
~B() {printf("~B()" "\n");}
};
int main()
{
A* a= new B;
a->~A();
}
Так же хочется заметить (этого нет в FAQ Страуструпа и в его книге), что есть такие вариации оператора new:
1. Обычный placement new
#include <new>
int *b = new(address) int(init_value);
Суть - создание объекта, но с использованием уже готового пространства address. Если реализации требуется сохранить какую-то метаинформацию, то может получиться так что b != address
2. Перегрузка new как глобальная функция
void* operator new(size_t sz) {return a.allocate(sz);}
void operator delete(void* ptr)
3. Перегрузка new с пользовательскими параметрами
Первый аргумент оператора размер в байтах. Вычисляется автоматически через sizeof, далее идёт список аргументов которые вы решили, что клиенты должны передавать.
void* operator new(size_t sz, Arena& a, float b) {return a.allocate(sz);}
Вызывается new так:
new(arg2, arg3) SOMETYPE()
delete программистом должен всегда вызываться стандартный.
Замечание: В принципе можно определить парный delete с таким же списком аргументов. Вызвать явно оператор delete с таким списком аргументов невозможно. Вызывается неявно только в одном случае - exception в конструкторе. Если парного нет, а исключение есть - ошибка компиляции. Правда в любом случае практического смысла мало.
4. Перегрузка оператора в классе
Можно определять new/delete в рамках класса. Хороший тон - делать статическим new/delete.
Однако даже если static не указан явно, оператор всё равно будет статический неявно.
5. Если перегружается оператор в базовом классе, то у наследников будет использоваться перегруженные операторы
Стандарт С++ гарантирует, что жизнь временного объекта (если он lvalue) продлевается до жизни любой ссылки ссылающейся на него! в том числе и константной! Это специально прописано в стандарте!
Ссылки на rvalue объекты (объекты у которых нельзя получить адрес) не продлевают время жизни временных объектов. Скорее всего компилятор не разрешит скомпилировать.
Однако компилятор может выявить только простые конструкции, если создать временный объект, и из этого временного объекта вызвать метод, который вернет ссылку на себя - то компилятор это определить уже не сможет.
В простых случаях на основе этого трюка можно захватывать возвращаемые временные объекты по константной ссылке, чтобы сократить кол-во конструкторов копирования.
rvalue - то от чего нельзя взять адрес (в 99% случаях это безымянные временные переменные) (и по сути может быть закодировано в коде генерируемых команд для процессора). Хороший контрпример то что является rvalue, но имеет имя -- this
lvalue - то что слева от оператора равно. Каждый lvalue преобразуется неявным образом в rvalue.
xvalue - то что вот-вот будет уничтожено, и объект для которого разумно использовать семантику move-а для забирания данных через && нотацию. (С++ 11)
rvo -- return value optimization. Разработчики компиляторов могут по факту выполнять или не выполнять разную оптимизацию в зависимости от разных настроек компилятора.
Rvalue Reference (or x-alue) - the temporary object will soon be destroyed. You should take data from it, because he will die very soon.
Such reference in C++11 has T&& signature. Overload function with Xvalue Reference (A&&) has priority under constant reference (const A&)
2. Не константная ссылка не может ссылаться на временную переменную
3. Несмотря на то, что временные объекты позволительно передавать только как const T& или T. Однако вызов неконстантных методов для временных объектов разрешен.
4. Инициализатор для const T& не обязан быть lvalue и даже иметь тип T. В таких случаях создаётся временная переменная, созданная для хранения инициализатора, существующая до конца области видимости ссылки.
См. Б. Страуструп, стр. 301
5.
const_cast - убирает const и volatile модификаторы с указателя
static_cast - приведение одного типа к другому посредством стандартных преобразования или преобразований, определенных пользователем.
Аргументом для dynamic_cast может быть нулевой указатель - результат этой операции есть нулевой указатель.
Аргументом для dynamic_cast должен быть полиморфный тип, это требование упрощает реализацию.
Результатом dynamic_cast не обязан быть полиморфный тип.
В случае невозможности понимания того к чему надо преобразовать тип (ромбовидное наследование с дубликатами) будет возвращен нулевой указатель. Так же будет возвращен нулевой указатель и для более простого основного случая.
reinterpret_cast - указания force приведения
Приведение типов в стиле С - пытается использовать static_cast, если не получается, использует reinterpret_cast.
Далее, если нужно, использует const_cast . (http://alenacpp.blogspot.ru/2005/08/c.html)
6. Аргументом для typeid может быть как полиморфный тип (в таком случае вытаскивается информация о реальном типе), так и не полиморфный тип (в том случае реализация наиболее примитивна. Никакая информация о реальном типе не вытаскивается.)
7. Использование using - директивы в немного не привычном стиле. В принципе using можно даже не писать.
struct A{
int a;
};
struct B : private A {
using A::a;
};
8. Параметры шаблона:
- Типы
- Интегральная константа
- Указатели и ссылки с external linkage-ем.
9. Пустой template<> синтаксически зарезервирован для явной специализации, и не может быть использован для создания шаблона без параметров.
Note that prior to C++11, a space was required between the two >’s. Without space, the C++98/C++03 compiler would see a >> (right-shift) token instead of two >’s.
Aren’t you lucky that it is no longer the case in C++11.
https://isocpp.org/wiki/faq/templates
Специализация шаблонного класса внутри шаблонного класса или метода внутри шаблонного класса по стандарту фактически невозможна
C++03, §14.7.3/2, C++03, §14.7.3/3
In C and C++, if you read a variable twice in an expression where you also write it, the result is undefined.
http://www.stroustrup.com/bs_faq2.html#evaluation-order
10. Шаблон может кастомизироваться шаблонным классом. Пример:
template<template<class, class> class H, class S>
struct T
{
H<S, S> my_pair;
};
T<std::pair, int> aa; (Б. Страуструп, Язык C++, Дополнение В. Параграф с шаблонами.)
Так же можно произвести классификацию того, чем может кастомизироваться шаблон
http://stackoverflow.com/questions/499106/what-does-template-unsigned-int-n-mean
Type Parameters:
- Types
- Templates (only classes, no functions)
Non-type Parameters:
- Pointers (pointers, member pointers to objects/functions that have external linkage)
- References (references to objects/functions that have external linkage)
- Integral constant expressions (It's a compile time constant, integer or enumeration)
Так же это может быть const переменная. Очень интересный faq про шаблоны https://isocpp.org/wiki/faq/templates
11. Cтандарт. С++ 2003. стр. 233. 13.5.1 - 13.5.7.
- Согласно стандарту нельзя перегружать операторы как static-методы класса. Возможно перегружать как функции и как не-статик методы класса.
- Нельзя перегружать следующие операторы: . :: .* ->* sizeof typeid ?:
- А операторы =, [], -> должны быть функциями-членами класса. Это гарантирует, что левый операнд будет lvalue
Например gcc 5.4 при попытке определить operator = как (свободную) функцию выдаёт:
error: ‘A& operator=(A&, A&)’ must be a nonstatic member function
12. Специализация шаблона может быть полная (тип int), частичная (тип который использует T как основу для чего-то другого). Однако общий шаблон должен быть объявлен прежде любой специализации хоть частичной, хоть полной.
Также частичная специализация работает только для классов, но не работает для функций
Ещё один момент связанный с шаблонной магией это SFINAE -- substitution failure is not an error. Если при подстановке шаблонных параметров получается некорректное выражение, это не является ошибкой. Компилятор должен проигнорировать шаблон. Некорректное выражение должно обнаружиться не в теле функции, когда конкретный шаблон уже выбран и продолжать поиск некуда, а в её прототипе.
template <class T>struct Single { Single() : var(T()) {} T var;};template <class T>struct Single<T*>{ Single() : var(T()) {} T var; int extra_for_ptr;};template <>struct Single<int>{ Single() : var(int()) {} int var; int extra_for_int;};int main(){ Single<int> a; a.extra_for_int = 1; Single<int*> b; b.extra_for_ptr = 1; return 0;}
13. Конструктор вводит в том числе неявное преобразование. Чтобы подавить это неявное преобразование конструктор
требуется объявлять с параметром explicit. ([1], стр.333)
14. При переопределении операторов требуется не забывать про квалификатор const.
К примеру оператор такого типа требуется для std::map
Опускание параметра может привести к тому, что Visual Studio даст невнятный комментарий про эту ошибку
error C2678: binary '<' : no operator found which takes a left-hand operand of
g++ лучше диагностирует эту проблему.
15. C++ не поддерживает forward declaration для nested классов
16. Автоматический вывод (deducation) параметров шаблона возможен только для функций и методов,
а вот для параметров шаблонных классов этот механизм не поддерживается (до С++17, с C++17 уже поддерживается)
// Sample 3. Выведение шаблона из списка аргументов функции // с более длинной до более короткой формы A->B->C// P.s. О сложных правилах допустимого выведения аргументов шаблонной // функции см. Страуструп, 932-933, В 13.4. Выведение аргумента шаблона функции.template <class T>void swap(T* a, T* b){ T temp = *a; *a = *b; *b = temp;}//Atemplate <>void swap<float>(float* a, float* b){ float temp_temp = *a; *a = *b; *b = temp_temp;}//Btemplate <>void swap<>(double* a, double* b){ double temp_temp = *a; *a = *b; *b = temp_temp;}//Ctemplate <>void swap(int* a, int* b){ int temp_temp = *a; *a = *b; *b = temp_temp;}int main(){ int a = 0, b = 0; swap(&a, &b); return 0;}
17. Для линковки функций в стиле C++ можно явно использовать extern "C++" void f()
См. ISO C++2003. 7.5.3. "Every implementation shall provide for linkage to functions written in the C programming language, "C", and linkage to C + + functions, "C++"."
Этим покрывается не только name mangling, но и call convention
https://isocpp.org/wiki/faq/pointers-to-members
18. Пример реализации проверки того, что тип является POD типом во время компиляции.
Реализация на основе switch. Вариант с goto компилятор из VisualStudio проглатывает, и сигнализирует только о warning-е.
Проверка основана на том, что в C++ не разрешается переход внутрь составного оператора, с обходом объвлений с инициализаторами.
#define CHECK_TYPE_IS_A_POD(TYPE)\
{\ switch(1)\ {\ case 1:\ TYPE IF_COMPILE_ERROR_THEN__##TYPE##__IS_NOT_A_POD;\ /* prune out any warnings about not usage */ \ IF_COMPILE_ERROR_THEN__##TYPE##__IS_NOT_A_POD = TYPE();\ case 2:\ ;\ }\}
18. Конструкция перехвата exception-ов из списка инициализации.
#include <stdio.h>
class C
{
public:
C() try : a()
{ puts("C()"); }
catch(...)
{ puts("WOW!"); }
int a;
};
int main()
{
C c;
return 0;
}
20. Пример реализации static_assert-а
template <bool>struct static_assert;template <>struct static_assert<true> {}; // only true is defined#define STATIC_ASSERT(x) static_assert<(x)>()
20. Private наследование кроме невидимости переменных пользователем производного класса членов вазового класса так же не даёт возможность использования неявного приведения типа.
// private наследованиеstruct A{int a;};struct B:private A{int b;};int main(){ B b; { A& ba = b; } // error { A& ba = (A&)b; } // ok return 0;}
21. Инициализация интегральных статических констант разрешена непосредственно в классе.
Согласно авторитетной книжке “Специальное издание. Б. Страуструп, 10.4.6.2” упоминается
…“Можно инициализировать член класса, являющийся статической константой интегрального типа. Если и только если вы делаете так, то нужно где-то один раз объявить этот член.”….
Согласно стандарту C++ Standard - ANSI ISO IEC 14882 2003 про это так же указано:
9.4.2 Static data members.
Subparagraph 4
If a static data member is of const integral or const enumeration
type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19). In
that case, the member can appear in integral constant expressions.
// test.cppclass AClass {public: static const int kVal = 1;};// Б. Страуструп 10.4.6.2// Можно инициализировать член класса, являющийся статической константой интегрального типа.
The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.
// Если и только если вы делаете так, то нужно где-то один раз объявить этот член.// const int AClass::kVal; // MUST HAVE по Страуструпу.
// Так же в 10.2.4 "Статические члены и функции и данные должны быть где-то определены" // Реальный мир - что с этой строчкой, что без неё компиляторы жуют успешно.// CYGWIN// g++ --version// g++ (GCC) 4.5.3// VS 2012int main(){ int a = AClass::kVal; return 0;}
22. Для того, чтобы в шаблонном классе объявить шаблонную-функцию-друг надо:
1. сделать так, чтобы функция была объявлена
2. Вставить в описание шаблонного класса Single декларацию дружбы friend void f_template<> (Single& arg); Здесь обязательны угловые скобки, так и отсутствие описания того, что Single есть шаблонный класс. По другому не жуётся (
// Друзья шаблонного класса - друзья-шаблоны со специальным синтакисом "<>" и друзья-не-шаблоныtemplate <class T> struct Single;template <class T> void f_template(Single<T>& arg); // **template <class T>struct Single {private: T var; friend void f_not_template(Single<int>& arg); // g++ 4.5.3 OK. MSVC2012- OK //friend void f_not_template(Single<T>& arg); // g++ 4.5.3 warning. MSVC2012- no warnings //friend void f_template (Single& arg); // g++ 4.5.3 error. MSVC2012- глупое сообщение об ошибке friend void f_template<> (Single& arg); // g++ 4.5.3 ok. MSVC2012 - OK.// В обоих случаях функция должна уже быть объявлена перед этим объвлением.
// В этом примере это сделано с помощью **
};void f_not_template(Single<int>& arg){ arg.var = 123; }template<class T>void f_template(Single<T>& arg){ arg.var = 123;}int main(){ Single<int> a; f_template(a); return 0;}
23. Ключевое слово export для разделения объявления/определения шаблона так пока и не сделано в основных средствах разработки http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1426.pdf
24. За такой код отрубают руки, но A* A = new class A() - позволяет вызвать конструктор класса A для переменной A, имеющий тип указателя на класс A. Может это и есть в стандарте.
25. Объявление и использование статических членов шаблонного класса.
// Статические члены шаблонного классаtemplate <class T>struct Single { Single() : var(def_val) {} static T* CreateInstanceOfT(); T var; static T def_val;};template <class T> T Single<T>::def_val(0);template <> int Single<int>::def_val(1);template <class T> T* Single<T>::CreateInstanceOfT() {return 0;}int main(){ Single<int>::CreateInstanceOfT(); return 0;}
25. Plain Old Data-Type
An aggregate is an array or a class (clause 9) with no user-declared constructors (12.1), no private or protected non-static data members (clause 11), no base classes (clause 10), and no virtual functions (10.3).
Типы агрегатов характерны тем, что объекты таких типов могут быть инициализированы в С++98/03 с помощью синтаксиса фигурных скобок, как инициализируются структуры.
value-initialized -- инициализация базовых арифметических типов значением 0, и вызов конструктора по-умолчанию для классов определенных пользователем.
Shortly Formal definition from the C++ standard (C++03 9 §4):
An aggregate class is called a POD if it has no user-defined copy-assignment operator and destructor and none of its nonstatic members is a non-POD class, array of non-POD, or a reference.
1. If you want to write a more or less portable dynamic library that can be used from C and even .NET you should try to make all your exported functions take and return only parameters of POD-types
2. The lifetime of objects of non-POD class type begins when the constructor has finished and ends when the destructor has finished. For POD classes, the lifetime begins when storage for the object is occupied and finishes when that storage is released or reused.
3. For objects of POD types it is guaranteed by the standard that when you memcpy the contents of your object into an array of char or unsigned char, and then memcpy the contents back into your object, the object will hold its original value
4. goto statement. As you may know, it is illegal (the compiler should issue an error) to make a jump via goto from a point where some variable was not yet in scope to a point where it is already in scope. This restriction applies only if the variable is of non-POD type.
5. It is guaranteed that there will be no padding in the beginning of a POD object.
26. Виды полной специализации для функции.
template <class T>void swap(T* a, T* b){ T temp = *a; *a = *b; *b = temp;}//Atemplate <>void swap<float>(float* a, float* b){ float temp_temp = *a; *a = *b; *b = temp_temp;}//Btemplate <>void swap<>(double* a, double* b){ double temp_temp = *a; *a = *b; *b = temp_temp;}//Ctemplate <>void swap(int* a, int* b){ int temp_temp = *a; *a = *b; *b = temp_temp;}
27. Шаблон с шаблонным параметром
// Шаблон с шаблонным параметром. Параметр шаблона может быть шаблонным параметром.#include <vector>#include <allocators>template <class T, template <class, class> class C>struct SingleContainerHolder { C<T, std::allocator<T>> container;};int main(){ SingleContainerHolder<int, std::vector> a; a.container.push_back(2); return 0;}
28. Экзотическая специализация шаблонного класса
template <int> struct A; template <> struct A<67>{};
29. Фикс стандартного поведения компилятора, не пытающегося найти перегруженную функцию в B.
class B {public: void f(int i) { cout << "f(int)\n"; }};class D : public B {public: // fix "strange" behaviour using B::f; void f(double d) { cout << "f(double)\n"; }};
31. Placement new/delete
class Arena {public: void* allocate(size_t) {return 0;} void deallocate(void*) {}};void* operator new (size_t sz, Arena& a) { return a.allocate(sz);}template <class T>void emulatePlacementDelete(T* pointer, Arena& a){ if (pointer) { pointer->~T(); a.deallocate(pointer); }}
// Placement new exist, "placement delete" only can be emulated { Arena a1; D* p1 = new(a1) D; emulatePlacementDelete(p1, a1); }
32. std::map<int, std::string> map; VS std::tr1::unordered_map<int, std::string> better_map;
Эвристика -- больше 10 000 элементов лучше юзать std::tr1::unordered_map
33. Can I define my own operators?// Sorry, no. The possibility has been considered several times, but each time I/we decided that the likely problems outweighed the likely benefits. http://www.stroustrup.com/bs_faq2.html
34. in C and C++, if you read a variable twice in an expression where you also write it, the result is undefined.
int i = 0; int aa = i++ + i++;
35. Философствование про Object:// Why doesn't C++ have a universal class Object?// -We don't need one: generic programming provides statically type safe alternatives in most cases// -There is no useful universal class: a truly universal carries no semantics of its own.// -Using a universal base class implies cost// Открытие про Generic-ки// Is "generics" what templates should have been?// No. generics are primarily syntactic sugar for abstract classes; // That is, with generics (whether Java or C# generics), // You program against precisely defined interfaces and typically pay the cost of virtual function calls and/or dynamic casts to use arguments.//
36. Можно считать, что typedef на forward-declaration классы не возможно.
37. Явное инстанцирование.
// "Я знаю примеры, когда помещение большой части инстанцирований // шаблонов сокращала время компиляции с нескольких часов до нескольких минут.", // стр. 944. В.13.10, Страуструп.// Я экспериментов не ставил. (Константин)// template class Single<int>; // Явное инстанцированиеint main(){ Mem m; // ** m.acuire<int> (); // ok MSVC2012, g++ 4.5.3 Mem::acuireS<int> (); // ok MSVC2012, g++ 4.5.3 Test::func<int> (); // ok MSVC2012, g++ 4.5.3 // В 13.6. Страуструп, спец. издание. 935-936. // "Явная квалификация необходима т.к. её параметр не может быть выведен. // Без template мы получим синтаксическую ошибку, которая будет воспринимать < как оператор меньше" // Практика правда показала адекватность работы (**), // и не совсем полную адекватность работы кода, который находится ниже /* m.template acuire<int> (); // ok MSVC2012. err in g++ 4.5.3. Mem::template acuireS<int>(); // ok MSVC2012. err in g++ 4.5.3. Test::template func<int> (); // SAME */ /* STILL NOT WORKS. Пример которым часто пугают на собеседованиях никак не хочет заводится. Test::template Obj <int> (); Test::Obj <int> (); */ return 0;}//------------------------------------------------------------------//