Участник:Yulya3102/Плюсы2сем — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(stack unwinding)
(перегрузка операторов, ADL)
 
(не показано 8 промежуточных версий этого же участника)
Строка 220: Строка 220:
 
=== callback'и, способы фиксации аргументов у callback'ов ===
 
=== callback'и, способы фиксации аргументов у callback'ов ===
 
=== наследование, виртуальные функции, таблицы виртуальных функций ===
 
=== наследование, виртуальные функции, таблицы виртуальных функций ===
 +
Виртуальный метод (виртуальная функция) — метод (функция) класса, который может быть переопределён в классах-наследниках так, что конкретная реализация метода для вызова будет определяться во время исполнения.
 +
 +
Базовый класс может и не предоставлять реализации виртуального метода, а только декларировать его существование. Такие методы без реализации называются «чистыми виртуальными» (перевод англ.  pure virtual) или абстрактными. Класс, содержащий хотя бы один такой метод, тоже будет абстрактным. Объект такого класса создать нельзя. Наследники абстрактного класса должны предоставить реализацию для всех его абстрактных методов, иначе они, в свою очередь, будут абстрактными классами.
 +
 +
Для каждого класса, имеющего хотя бы один виртуальный метод, создаётся таблица виртуальных методов. Каждый объект хранит указатель на таблицу своего класса. Для вызова виртуального метода используется такой механизм: из объекта берётся указатель на соответствующую таблицу виртуальных методов, а из неё, по фиксированному смещению, — указатель на реализацию метода, используемого для данного класса. При использовании множественного наследования или интерфейсов ситуация несколько усложняется за счёт того, что таблица виртуальных методов становится нелинейной.
 +
 
=== dynamic_cast ===
 
=== dynamic_cast ===
=== проблемы сишного typecast'а с неполными типами и несколькими базами, static_cast ===
+
В отличие от обычного приведения типа в стиле Си, проверка корректности приведения типов производится во время выполнения программы. Оператор dynamic_cast может быть применён к указателям или ссылкам. В случае если осуществляется преобразование указателя к типу данных, который не является фактическим типом объекта, в результате преобразования будет получен нулевой указатель. При работе с ссылками при невозможности преобразования типа будет сгенерировано исключение std::bad_cast.
=== const_cast, reinterpret_cast ===
+
 
 +
=== проблемы сишного typecast'а с неполными типами и несколькими базами ===
 +
Что делает приведение типов в стиле С: пытается использовать static_cast, если не получается, использует reinterpret_cast. Далее, если нужно, использует const_cast .
 +
=== static_cast ===
 +
Может быть использован для приведения одного типа к другому. Если это встроенные типы, то будут использованы встроенные в C++ правила их приведения. Если это типы, определенные программистом, то будут использованы правила приведения, определенные программистом.
 +
static_cast между указателями корректно, только если один из указателей - это указатель на void или если это приведение между объектами классов, где один класс является наследником другого. То есть для приведения к какому-либо типу от void*, который возвращает malloc, следует использовать static_cast.
 +
int * p = static_cast<int*>(malloc(100));
 +
Если приведение не удалось, возникнет ошибка на этапе компиляции. Однако, если это приведение между указателями на объекты классов вниз по иерархии и оно не удалось, результат операции undefined. То есть, возможно такое приведение: static_cast<Derived*>(pBase), даже если pBase не указывает на Derived, но программа при этом будет вести себя странно.
 +
 
 +
=== const_cast ===
 +
Самое простое приведение типов. Убирает так называемые cv спецификаторы (cv qualifiers), то есть const и volatile. volatile встречается не очень часто, так что более известно как приведение типов, предназначенное для убирания const. Если приведение типов не удалось, выдается ошибка на этапе компиляции.
 +
При использовании остальных приведений типов cv спецификаторы останутся как были.
 +
int i;
 +
const int * pi = &i;
 +
// *pi имеет тип const int,
 +
// но pi указывает на int, который константным не является
 +
int* j = const_cast<int *> (pi);
 +
 
 +
=== reinterpret_cast ===
 +
Самое нахальное приведение типов. Не портируемо, результат может быть некорректным, никаких проверок не делается. Считается, что вы лучше компилятора знаете как на самом деле обстоят дела, а он тихо подчиняется. Не может быть приведено одно значение к другому значению. Обычно используется, чтобы привести указатель к указателю, указатель к целому, целое к указателю. Умеет также работать со ссылками.
 +
 
 +
reinterpret_cast<whatever *>(some *)
 +
reinterpret_cast<integer_expression>(some *)
 +
reinterpret_cast<whatever *>(integer_expression)
 +
 
 +
Чтобы использовать reinterpret_cast нужны очень и очень веские причины. Используется, например, при приведении указателей на функции.
 +
 
 
=== зачем нужно виртуальное наследование (несколько баз с разными адресами у одного класса, typecast'ы, исключения) ===
 
=== зачем нужно виртуальное наследование (несколько баз с разными адресами у одного класса, typecast'ы, исключения) ===
 
=== виртуальное наследование ===
 
=== виртуальное наследование ===
 
=== зачем нужны namespace'ы ===
 
=== зачем нужны namespace'ы ===
=== namespace'ы, using declaration, using directive, namespace aliases ===
+
Namespaces allow to group entities like classes, objects and functions under a name. This way the global scope can be divided in "sub-scopes", each one with its own name.
 +
 
 +
=== namespace'ы, using declaration ===
 +
=== using directive ===
 +
using namespace namespace_name;
 +
Делает все объекты из namespace_name доступными в текущем scope
 +
 
 +
namespace a {
 +
    int v;
 +
}
 +
 +
namespace b {
 +
    int v;
 +
}
 +
 +
И два случая:
 +
 +
{
 +
    using a::v;
 +
    using namespace b; // ошибки нет
 +
}
 +
 +
{
 +
    using a::v;
 +
    using b::v; // ошибка из-за повторного объявления
 +
}
 +
 
 +
=== namespace aliases ===
 +
We can declare alternate names for existing namespaces according to the following format:
 +
 
 +
namespace new_name = current_name;
 +
 
 
=== перегрузка операторов, ADL ===
 
=== перегрузка операторов, ADL ===
 +
In the C++ programming language, argument-dependent lookup (ADL), or argument-dependent name lookup, applies to the lookup of an unqualified function name depending on the types of the arguments given to the function call.
 +
namespace NS
 +
{
 +
    class A {};
 +
    void f( A *&, int ) {}
 +
}
 +
int main()
 +
{
 +
    NS::A *a;
 +
    f( a, 0 );    //calls NS::f
 +
}
 +
 
=== зачем нужны шаблоны ===
 
=== зачем нужны шаблоны ===
 
=== базовое представление о шаблонах ===
 
=== базовое представление о шаблонах ===

Текущая версия на 23:39, 9 марта 2013

Содержание

i8086

регистры общего назначения, сегментные регистры, регистр флагов

некоторые распространенные команды

команды для работы со стеком

соглашения вызова функций

32-битные регистры

stack frame

ошибки: возврат указателя на локальную переменную из функции, buffer-overrun для объектов в стеке

stack-smash protector

toolchain: препроцессор, транслятор, компоновщик

модификатор static

Local scope

//область видимости — блок

while (true)
{
    static int i = 0;
    i++;
}

Переменная i будет создана лишь один раз, и её значение будет храниться в течение всего времени выполнения программы, несмотря на выход за пределы области видимости. Тот же пример с выводом для наглядности.

Function scope

//область видимости — функция

void foo()
{
    static int i = 0;
    i++;
}

Аналогично, переменная i сохраняется между вызовами foo(), но видна только в пределах блока, в котором определена.

File scope

//область видимости — файл

static int global_static_local_to_this_file;

Очевидно, эта переменная будет видна повсюду в файле и только в нём.

Class scope

//область видимости — класс

Статические данные можно получить так же, как и нестатические, с помощью операторов . и ->. Но к ним также можно обращаться с помощью оператора ::, используя имя класса. Очевидно, создание объекта данного класса в данном случае не требуется.

Статические данные разделяются между всеми экземплярами класса.

Зачем это нужно? Например, так можно создать синглтон:

class singleton
{
    private:
        singleton(){}
    public:
        static singleton& instance()
        {
            static singleton inst;
            return inst;
        }
};

Объявление статической переменной внутри определения класса не является её определением. Если не инициализировать значение статической переменно, то по умолчанию будет присвоено значение 0. Внутри определения класса можно инициализировать переменные типов int, char, long, bool, а так же типов из пространства std.

Статические переменные не могут содержаться в локальных классах и не могут быть mutable.

Статические методы не имеют указателя this, поэтому в таких методах нельзя обращаться к нестатическим данным класса. К статическим методам можно обращаться через . и -> по имени объекта и по имени класса через ::.

Статические методы не могут быть виртуальными, не могут быть const и volatile. Локальные класса не могут содержать статические методы.

модификатор inline

Если часто повторяется обращение к очень маленькой функции, то стоит задуматься о стоимости вызова функции. Можно рассмотреть возможность спецификации функции как inline-подставляемой. В этом случае компилятор сгенерирует для функции соответствующий код в месте её вызова.

Для того, чтобы указать компилятору на своё желание сделать функцию inline-подставляемой, нужно поставить ключевое слово inline или включить определение функции в описание класса.

one definition rule (ODR)

Вкратце, положения ODR выглядят так:

  1. В пределах любой единицы трансляции шаблон, тип данных, функция, или объект не могут иметь более одного определения. Некоторые могут иметь неограниченное число объявлений. Определение порождает сущность.
  2. В пределах программы (совокупности всех единиц трансляции) объект или не-inline функция не могут иметь больше одного определения; если объект или функция используются, у каждого из них должно быть строго по единственному определению. Можно объявить объект или функцию, которые не будут использованы в программе, в этом случае не потребуется и их определения. Ни в коем случае не должно быть более одного определения.
  3. Некоторые сущности, например, типы, шаблоны или внешние inline функции, могут определяться в более чем одной единице трансляции. Для данной сущности, каждое определение должно быть одним и тем же. Объекты или функции, не являющиеся внешними, в разных единицах трансляции имеют разные сущности, даже если их имена и типы совпадают.

Некоторые нарушения ODR могут быть обнаружены компилятором. Остальные, а это в первую очередь касается программ, состоящих из нескольких небольших файлов, пропускаются (программист должен самостоятельно о них позаботиться).

Например:

 // file1.c:
     int a = 1;
     int b = 1;
     extern int c;
 // file2.c:
     int a;
     extern double b;
     extern int c;

В данном примере 3 ошибки: a определено дважды (int a; является определением, которое означает int a=0;), b описано дважды с разными типами, а c описано дважды, но не определено. Эти виды ошибок (ошибки компоновки) не могут быть обнаружены компилятором, который за один раз видит только один файл. Компоновщик, однако, их обнаруживает.

классы

Класс — пользовательский тип, объявленный с помощью struct, class или union.

конструкторы

Конструктор вызывается при создании объекта для его инициализации. Обозначается как одноимённая классу функция, не имеющая типа возвращаемого значения. Класс может иметь сколько угодно конструкторов с разными наборами параметров. Конструктор без параметров (или все аргументы которого имеют параметры по умолчанию) называется конструктором по умолчанию, а конструктор с первым параметром-ссылкой на тот же класс — конструктором копирования.

Array a(5); // вызывается Array::Array(int)
Array b;    // вызывается Array::Array()
Array c(a); // вызывается Array::Array(const Array&)
Array d=a;  // вызывается Array::Array(const Array&)
b=c;        // происходит вызов оператора =

Если в классе нет явно объявленных конструкторов, то он имеет неявно объявленный конструктор без параметров, который конструирует подобъекты классов-родителей и инициализирует поля класса с помощью конструкторов по умолчанию.

Конструктор копирования также неявно объявляется при отсутствии явного. Он выполняет копирование всех объявленных полей с помощью соответствующих конструкторов копирования.

Конструкторы не могут быть виртуальными.

деструкторы

Деструктор вызывается для уничтожения объекта класса. Обозначается как имя класса, перед которым ставится тильда. Деструктор не имеет типа возвращаемого значения и принимаемых аргументов. Класс может иметь только один деструктор.

Если деструктор не объявлен явно, то класс имеет неявно объявленный деструктор.

Деструктор может быть виртуальным (и обычно так и объявляется, чтобы гарантировать правильное уничтожение доступного по ссылке или указателю объекта независимо от того, какого типа ссылка или указатель).

const

Переменные

Очевидно, const переменные — это переменные, которые нельзя менять. Инициализируются в момент объявления.

const int p = 1; //инициализация в момент объявления, всё ок
const int q;
q = 5;           //а вот так уже нельзя

Функции-члены класса

Такие функции не могут менять поля класса, не объявленные как static или mutable, не могут возвращать не const ссылки и указатели на поля класса и не могут вызывать неконстантные функции класса.

ссылки

разница между ссылками и указателями

[1]

l-value, r-value

[2]

перегрузка операторов

[3]

оператор присваивания

Оператор присваивания генерируется компилятором. Генерируемый компилятором код выполняет поверхностное копирование всех полей (что может быть не очень подходящим вариантом).

Оператор присваивания вызывается на уже проинициализированном объекте, поэтому нужно освобождать ранее занятые ресурсы. А ещё надо обрабатывать самоприсваивание.

разница между конструктором копирования и оператором присваивания

список инициализации

порядок инициализации/разрушения полей класса

Поля инициализируются в том порядке, в котором они объявлены, даже если в списке инициализации они указаны в другом порядке. Разрушаются в порядке, обратном порядку объявления.

volatile

директивы препроцессора

#include <>

#include ""

#define

#if

#else

#endif

#elif

#ifdef

#ifndef

#undef

include guards

#pragma once

#pragma once vs include guards

preprocessor pitfalls

[4]

циклический экспанд макросов

циклические зависимости на хедерах

неполные типы

[5]

name mangling

перегрузка функций

исключения

RAII

RAII (Resource Acquisition Is Initialization) — это концепция, используемая при написании безопасного при исключениях кода. Её смысл заключается в том, что получение некоторого ресурса совмещается с инициализацией, а освобождение — с уничтожением объекта.

гарантии безопасности исключений

Существует три типа гарантий безопасности исключений.

  • Базовая гарантия: при возникновении любого исключения состояние программы должно оставаться согласованным.
  • Строгая гарантия: если при выполнении операции возникает исключение, то это не должно оказать какого-либо влияния на состояние приложения.
  • Гарантия отсутствия исключений: ни при каких обстоятельствах метод не будет генерировать исключения.

extern "C"

исключения в конструкторах

исключения в деструкторах

stack unwinding

void func( int x )
{
    char* pleak = new char[1024]; // might be lost => memory leak
    std::string s( "hello world" ); // will be properly destructed

    if ( x ) throw std::runtime_error( "boom" );

    delete [] pleak; // will only get here if x != 0
}

int main()
{
    try
    {
        func( 10 );
    }
    catch ( const std::exception& e )
    {
        return 1;
    }

    return 0;
}

Here memory allocated for pleak will be lost if exception is thrown, while memory allocated to s will be properly released by std::string destructor in any case. The objects allocated on the stack are "unwound" when the scope is exited (here the scope is of the function func.) This is done by the compiler inserting calls to destructors of automatic (stack) variables.

возврат объектов из функции по значению, RVO

передача аргументов по значению, r-value/l-value

callback'и, способы фиксации аргументов у callback'ов

наследование, виртуальные функции, таблицы виртуальных функций

Виртуальный метод (виртуальная функция) — метод (функция) класса, который может быть переопределён в классах-наследниках так, что конкретная реализация метода для вызова будет определяться во время исполнения.

Базовый класс может и не предоставлять реализации виртуального метода, а только декларировать его существование. Такие методы без реализации называются «чистыми виртуальными» (перевод англ. pure virtual) или абстрактными. Класс, содержащий хотя бы один такой метод, тоже будет абстрактным. Объект такого класса создать нельзя. Наследники абстрактного класса должны предоставить реализацию для всех его абстрактных методов, иначе они, в свою очередь, будут абстрактными классами.

Для каждого класса, имеющего хотя бы один виртуальный метод, создаётся таблица виртуальных методов. Каждый объект хранит указатель на таблицу своего класса. Для вызова виртуального метода используется такой механизм: из объекта берётся указатель на соответствующую таблицу виртуальных методов, а из неё, по фиксированному смещению, — указатель на реализацию метода, используемого для данного класса. При использовании множественного наследования или интерфейсов ситуация несколько усложняется за счёт того, что таблица виртуальных методов становится нелинейной.

dynamic_cast

В отличие от обычного приведения типа в стиле Си, проверка корректности приведения типов производится во время выполнения программы. Оператор dynamic_cast может быть применён к указателям или ссылкам. В случае если осуществляется преобразование указателя к типу данных, который не является фактическим типом объекта, в результате преобразования будет получен нулевой указатель. При работе с ссылками при невозможности преобразования типа будет сгенерировано исключение std::bad_cast.

проблемы сишного typecast'а с неполными типами и несколькими базами

Что делает приведение типов в стиле С: пытается использовать static_cast, если не получается, использует reinterpret_cast. Далее, если нужно, использует const_cast .

static_cast

Может быть использован для приведения одного типа к другому. Если это встроенные типы, то будут использованы встроенные в C++ правила их приведения. Если это типы, определенные программистом, то будут использованы правила приведения, определенные программистом. static_cast между указателями корректно, только если один из указателей - это указатель на void или если это приведение между объектами классов, где один класс является наследником другого. То есть для приведения к какому-либо типу от void*, который возвращает malloc, следует использовать static_cast.

int * p = static_cast<int*>(malloc(100));

Если приведение не удалось, возникнет ошибка на этапе компиляции. Однако, если это приведение между указателями на объекты классов вниз по иерархии и оно не удалось, результат операции undefined. То есть, возможно такое приведение: static_cast<Derived*>(pBase), даже если pBase не указывает на Derived, но программа при этом будет вести себя странно.

const_cast

Самое простое приведение типов. Убирает так называемые cv спецификаторы (cv qualifiers), то есть const и volatile. volatile встречается не очень часто, так что более известно как приведение типов, предназначенное для убирания const. Если приведение типов не удалось, выдается ошибка на этапе компиляции. При использовании остальных приведений типов cv спецификаторы останутся как были.

int i;
const int * pi = &i;
// *pi имеет тип const int,
// но pi указывает на int, который константным не является
int* j = const_cast<int *> (pi);

reinterpret_cast

Самое нахальное приведение типов. Не портируемо, результат может быть некорректным, никаких проверок не делается. Считается, что вы лучше компилятора знаете как на самом деле обстоят дела, а он тихо подчиняется. Не может быть приведено одно значение к другому значению. Обычно используется, чтобы привести указатель к указателю, указатель к целому, целое к указателю. Умеет также работать со ссылками.

reinterpret_cast<whatever *>(some *)
reinterpret_cast<integer_expression>(some *)
reinterpret_cast<whatever *>(integer_expression)

Чтобы использовать reinterpret_cast нужны очень и очень веские причины. Используется, например, при приведении указателей на функции.

зачем нужно виртуальное наследование (несколько баз с разными адресами у одного класса, typecast'ы, исключения)

виртуальное наследование

зачем нужны namespace'ы

Namespaces allow to group entities like classes, objects and functions under a name. This way the global scope can be divided in "sub-scopes", each one with its own name.

namespace'ы, using declaration

using directive

using namespace namespace_name;

Делает все объекты из namespace_name доступными в текущем scope

namespace a {
    int v;
}

namespace b {
    int v;
}

И два случая:

{
    using a::v;
    using namespace b; // ошибки нет
}

{
    using a::v;
    using b::v; // ошибка из-за повторного объявления
}

namespace aliases

We can declare alternate names for existing namespaces according to the following format:

namespace new_name = current_name;

перегрузка операторов, ADL

In the C++ programming language, argument-dependent lookup (ADL), or argument-dependent name lookup, applies to the lookup of an unqualified function name depending on the types of the arguments given to the function call.

namespace NS 
{
   class A {};
   void f( A *&, int ) {}
}
int main() 
{
   NS::A *a;
   f( a, 0 );    //calls NS::f
}

зачем нужны шаблоны

базовое представление о шаблонах

два способа реализации шаблонов: Cfront-like, Borland-like, преимущества и недостатки обоих

специализация, частичная специализация

type_traits'ы

This header defines a series of classes to obtain type information on compile-time.

The header contains: Helper classes: Standard classes to assist in creating compile-time constants. Type traits: Classes to obtain characteristics of types in the form of compile-time constant values. Type transformations: Classes to obtain new types by applying specific transformations to existing types.

Контейнеры STL

Sequences: vector, list, deque

Associative Containers: [unordered][_multi]{set/map}

Итераторы, категории итераторов (Input, Output, Forward, Bidirectional, Random Access)

Примеры алгоритмов

find, find_if, search

remove, remove_if, прием с erase/remove_if

sort, stable_sort, partial_sort, nth_element

Много разговоров на тему того, что хоть явных ограничений на параметры шаблона нет, это не означает что их нет совсем.

копируемость/присваиваемость

требования к operator==

требования к operator<

согласованность operator== и operator<

разница между операторами внутри класса и снаружи (неявное приведение типов)

safe-bool

использование type_traits: SFINAE, tag-dispatching на примере STL

two-phase name lookup

STL: back-inserter, lower_bound, heap_*, priority_queue

const на примере STL

Concepts

anonymous namespaces

You can declare an unnamed namespace as a superior alternative to the use of global static variable declarations.

namespace { namespace-body }

An unnamed-namespace-definition having the syntax shown above behaves as if it were replaced by:

namespace unique { namespace-body }
using namespace unique;

Each unnamed namespace has an identifier, assigned and maintained by the program and represented here by unique, that differs from all other identifiers in the entire program. For example:

// unnamed_namespaces.cpp
// C2872 expected
namespace { int i; }          // unique::i
void f() { i++; }             // unique::i++

namespace A {
   namespace {
       int i;      // A::unique::i
       int j;      // A::unique::j
   }
}

using namespace A;

void h()
{
   i++;            // C2872: unique::i or A::unique::i
   A::i++;         // C2872: A::i undefined
   j++;            // A::unique::j++
}

Unnamed namespaces are a superior replacement for the static declaration of variables. They allow variables and functions to be visible within an entire translation unit, yet not visible externally. Although entities in an unnamed namespace might have external linkage, they are effectively qualified by a name unique to their translation unit and therefore can never be seen from any other translation unit.