Про глобальные константы ч3.
Константы внутри класса, объявленные как
До С++17 мы использовали весьма унылый паттерн, объявляя
Используя inline, мы можем объявить и определить статическую константу в классе одновременно:
Константы внутри класса, объявленные как
static, имеют ту же область видимости, что и глобальные константы и inline также упростил их определение в C++17. До С++17 мы использовали весьма унылый паттерн, объявляя
static в определении класса (заголовке) и определяя в единственном cpp файле (обычно для класса):// заголовок
class MyClass
{
static const std::string MUFFIN_NAME;
};
// cpp (единственный)
const std::string MyClass::MUFFIN_NAME = "Rum baba";
Используя inline, мы можем объявить и определить статическую константу в классе одновременно:
// заголовок
class MyClass
{
static inline const std::string MUFFIN_NAME = "Rum baba";
};
// cpp - ничего не надо
❤3👌1
Виртуальные функции распознаются во время выполнения, только если вызов осуществляется через указатель или ссылку. В этом случае статический тип отличается от динамического.
Как видим, при вызове по значению, будет использован статический тип, то есть вызовется метод того объекта, тип которого имеет переменная.
#include <iostream>
#include <string>
struct Base
{
virtual void run(const std::string & f) const
{
std::cout << "Base::run() " << f << std::endl;
}
virtual ~Base() = default;
};
struct Derived : public Base
{
virtual void run(const std::string& f) const override
{
std::cout << "Derived::run() " << f << std::endl;
}
};
void f_pointer(const Base* b)
{
b->run("pointer");
}
void f_reference(const Base& b)
{
b.run("reference");
}
void f_value(const Base b)
{
b.run("value");
}
int main()
{
Derived d;
f_pointer(&d); // Derived::run() pointer
f_reference(d); // Derived::run() reference
f_value(d); // Base::run() value
return 0;
}
Как видим, при вызове по значению, будет использован статический тип, то есть вызовется метод того объекта, тип которого имеет переменная.
❤2
Про глобальные константы ч4.
До С++17 для создания одной константы можно использовать ключевое слово extern, такой код мы пишем в заголовке:
Код выше с extern - это объявление, а не определение. А вот с inline это было определение. Объявление говорит, что где-то есть переменная с типом X.
Не смотря на то, что C++ требует уникальное определение объекта, объявлений может быть несколько.
Для того, чтобы использовать x, нам нужно где-то его определить. Это обычно используется в одном из .cpp файлов, в каком именно - решать вам, но лучше в том, где это будет иметь больше смысла.
Так как в заголовке только объявление, то и конструктор не принимает параметров (так как он там не вызывается). Вместо этого конструктор принимает параметры в одном из .cpp.
Важно: определение должно быть точно одно. Если определения нет, то будет "undefined external symbol", если больше одного, то - "duplicate external symbol".
До С++17 для создания одной константы можно использовать ключевое слово extern, такой код мы пишем в заголовке:
extern const X x;
Код выше с extern - это объявление, а не определение. А вот с inline это было определение. Объявление говорит, что где-то есть переменная с типом X.
Не смотря на то, что C++ требует уникальное определение объекта, объявлений может быть несколько.
Для того, чтобы использовать x, нам нужно где-то его определить. Это обычно используется в одном из .cpp файлов, в каком именно - решать вам, но лучше в том, где это будет иметь больше смысла.
Так как в заголовке только объявление, то и конструктор не принимает параметров (так как он там не вызывается). Вместо этого конструктор принимает параметры в одном из .cpp.
Важно: определение должно быть точно одно. Если определения нет, то будет "undefined external symbol", если больше одного, то - "duplicate external symbol".
Есть такой код стайл, например в том же Гугле, писать имя константы с k.
Например, так:
Всегда жутко это бесило, но стало интересно, откуда k? Первый вариант, который пришел в голову, что это константа (ведь Брин русский 😊). Гуглежь показал, что это не так далеко от правды.
На SO пишут, что в ранее популярной (и сейчас встречающейся, привет MS) венгерской нотации в начале имени переменной пишут сокращенный тип, например:
Если же взять для имени константы c, то можно спутать с char или count. Кто-то умный предложил взять k, тк в немецком "konstant" это внезапно константа. Так что к могла бы и из русского придти)
P.S. я бы дописывала в начало const: const int constSize = 10;
Например, так:
const int kDaysInAWeek = 7;
Всегда жутко это бесило, но стало интересно, откуда k? Первый вариант, который пришел в голову, что это константа (ведь Брин русский 😊). Гуглежь показал, что это не так далеко от правды.
На SO пишут, что в ранее популярной (и сейчас встречающейся, привет MS) венгерской нотации в начале имени переменной пишут сокращенный тип, например:
int iSize;
bool bBusy;
void *pFoo; // pointer
char chInitial;
Если же взять для имени константы c, то можно спутать с char или count. Кто-то умный предложил взять k, тк в немецком "konstant" это внезапно константа. Так что к могла бы и из русского придти)
P.S. я бы дописывала в начало const: const int constSize = 10;
Среднее арифметическое двух беззнаковых - тема частая, самый канонически пример - это бинарный (двоичный) поиск:
Но тут есть такая проблемка: результат может переполниться. Например, если целые 32-битные, то среднее mid(0x80000000U, 0x80000000U) вернет 0. Не то, что мы бы хотели.
В случае, если мы знаем какое число больше (а мы часто знаем), среднее можно вычислить без переполнения так:
Есть и другие формулы, для некоторых даже есть патент.
unsigned mid(unsigned low, unsigned high)
{
return (low + high) / 2;
}
Но тут есть такая проблемка: результат может переполниться. Например, если целые 32-битные, то среднее mid(0x80000000U, 0x80000000U) вернет 0. Не то, что мы бы хотели.
В случае, если мы знаем какое число больше (а мы часто знаем), среднее можно вычислить без переполнения так:
unsigned mid(unsigned low, unsigned high)
{
return low + (high - low) / 2;
}
Есть и другие формулы, для некоторых даже есть патент.
👍6🔥2
А сегодня, детишки, я расскажу вам сказку про С++ паттерн под названием Return Type Resolver (переведете сами).
Но давайте начнем с проблемы (вот бы все так делали).
Допустим, нам нужно написать 2 функции конвертирования строки в число, одну для int, вторую для float, пусть так:
Но вот беда в том, что только по типу возвращаемого значения функцию перегрузить нельзя. Ну и ладно, назовем функции по разному и пофиг, хнык-хнык.
Но мы же в сказке и добро должно победить! Решение - параметризовать оператор приведения типа, например вот так:
Алярм: требует C++17.
Но давайте начнем с проблемы (вот бы все так делали).
Допустим, нам нужно написать 2 функции конвертирования строки в число, одну для int, вторую для float, пусть так:
int from_string(const char *str) { return std::stoi(str); }
float from_string(const char *str) { return std::stof(str); } // errorНо вот беда в том, что только по типу возвращаемого значения функцию перегрузить нельзя. Ну и ладно, назовем функции по разному и пофиг, хнык-хнык.
Но мы же в сказке и добро должно победить! Решение - параметризовать оператор приведения типа, например вот так:
class from_string
{
const string m_str;
public:
from_string(const char *str) : m_str(str) {}
template <typename type>
operator type(){
if constexpr(is_same_v<type, float>) return stof(m_str);
else if (is_same_v<type, int>) return stoi(m_str);
}
};
int n_int = from_string("123");
float n_float = from_string("123.111");
Алярм: требует C++17.
👍7🔥2🤔1
Если вы думаете, что паттерн Return Type Resolver из предыдущего поста бесполезный и плохой, то возможно вас удивит, что он может быть использовать для реализации nullptr, чтобы выводить нулевой указатель корректного типа в зависимости от типа переменной, к которой nullptr присваивается.
Простой вариант реализации мог бы выглядеть так:
А вот пример реализации nullptr из llvm (впрочем, код очень старый)
Простой вариант реализации мог бы выглядеть так:
struct nullptr_t
{
void operator&() const = delete; // Can't take address of nullptr
template<class T>
inline operator T*() const { return 0; }
template<class C, class T>
inline operator T C::*() const { return 0; }
};
nullptr_t nullptr;
А вот пример реализации nullptr из llvm (впрочем, код очень старый)
GitHub
libcxx/include/__nullptr at master · llvm-mirror/libcxx
Project moved to: https://github.com/llvm/llvm-project - llvm-mirror/libcxx
❤1
Как все мы знаем, protected используется, чтоб предоставить наследникам доступ к защищенным членам, но закрыть от общего доступа. Но всегда ли производный класс может обратиться к protected членам? Давайте посмотрим на код ниже:
В первой реализации функции f() класс-наследник Derived спокойно может обратиться к защищенному и унаследованному полю _protMember через ссылку на Derived.
А вот во втором методе f() доступ к тому же полю через ссылку на Base уже не доступен - код не скомпилится.
Таким образом, производный член класса (или его friend) может обратиться к защищенным членам базового класса только через объект производного.
class Base
{
protected:
int _protMember;
};
class Derived : public Base
{
private:
void f(Derived& d);
void f(Base& b);
int _privMember;
};
void Derived::f(Derived& d)
{
d._privMember = 0;
d._protMember = 0; // OK
}
void Derived::f(Base& b)
{
b._protMember = 0; // Не скомпилится
}
В первой реализации функции f() класс-наследник Derived спокойно может обратиться к защищенному и унаследованному полю _protMember через ссылку на Derived.
А вот во втором методе f() доступ к тому же полю через ссылку на Base уже не доступен - код не скомпилится.
Таким образом, производный член класса (или его friend) может обратиться к защищенным членам базового класса только через объект производного.
Немного про закрытое (private) наследование.
Спецификатор доступа наследования никак не влияет на возможность производного класса обратиться к членам его собственного прямого базового класса. Приватное наследование на запретит доступ непосредственным (!) детям к protected, любое наследование не разрешит доступ к private. Класс сам определяет права доступа наследникам:
Это все очевидно, но интересен вопрос, а что если добавить в цепочку наследования еще классов с разными доступами наследования:
Как видим, protected так и остался доступен для второго уровня наследника при public наследовании, а вот исправить public наследованием от private наследования уже нельзя - protected член стал безвозвратно недоступным из-за первого private наследования.
Спецификатор доступа наследования никак не влияет на возможность производного класса обратиться к членам его собственного прямого базового класса. Приватное наследование на запретит доступ непосредственным (!) детям к protected, любое наследование не разрешит доступ к private. Класс сам определяет права доступа наследникам:
class Base
{
protected:
int _protMember;
};
class PublicDerived : public Base
{
public:
int f() { return _protMember; } // OK
};
class PrivateDerived : private Base
{
public:
int f() { return _protMember; } // OK
};
Это все очевидно, но интересен вопрос, а что если добавить в цепочку наследования еще классов с разными доступами наследования:
class DerivedFromPublic : public PublicDerived
{
public:
int f2() { return _protMember; } // OK
};
class DerivedFromPrivate : public PrivateDerived
{
public:
int f2() { return _protMember; } // NOT OK
};
Как видим, protected так и остался доступен для второго уровня наследника при public наследовании, а вот исправить public наследованием от private наследования уже нельзя - protected член стал безвозвратно недоступным из-за первого private наследования.
Член производного класса с тем же именем, что и в базовом, скрывает член базового класса (скрывает к нему доступ).
При этом важно, что "скрытие" не означает замену. Член базового класса продолжает существовать (см sizeof), а для доступа к члену базового класса можно использовать оператор области видимости (Base::).
#include <iostream>
struct Base
{
Base(int i) : _protMember(i) {}
int get() const { return _protMember; }
protected:
int _protMember;
};
struct Derived : public Base
{
Derived(int i) : _protMember(i), Base(0) {}
int get() const { return _protMember; }
int getBase() const { return Base::_protMember; }
protected:
int _protMember;
};
struct Derived2 : public Base
{
Derived2(int i) : Base(i) {}
};
int main()
{
Derived d(42);
std::cout << d.get() << " " << d.getBase() << std::endl; // 42 0
std::cout << sizeof(Derived) << " " << sizeof(Derived2) << std::endl; // 8 4
return 0;
}
При этом важно, что "скрытие" не означает замену. Член базового класса продолжает существовать (см sizeof), а для доступа к члену базового класса можно использовать оператор области видимости (Base::).
👍2
В С++17 в std::map добавили метод try_emplace, который похож на emplace, но он создает элемент, только если такого ключа еще нет (есть еще другие особенности). Обычный emplace создает элемент, даже если вставка не произойдет. Таким образом, try_emplace позволяет ускорить код, если создание объекта тяжелое. Пример:
#include <iostream>
#include <map>
struct Value
{
Value(int i = 0)
{
std::cout << "Value " << i << std::endl;
}
Value(const Value& other)
{
std::cout << "Value copy ctor" << std::endl;
}
Value& operator=(const Value& other)
{
std::cout << "Value operator=" << std::endl;
return *this;
}
};
struct Key
{
Key(int i = 0) : _i(i)
{
std::cout << "Key " << i << std::endl;
}
bool operator<(const Key& other) const
{
return _i < other._i;
}
int _i;
};
int main()
{
// Key 1
// Value 1
// Value copy ctor
std::map<Key, Value> map = {
{1, 1}
};
std::cout << std::endl;
// Value 2
// Key 2
// Value 0 - вызвался дефолтный конструктор, он требуется для operator[]
// Value operator=
map[2] = 2;
std::cout << std::endl;
// Key 3
// Value 3
// Value copy ctor
map.insert({3, 3});
std::cout << std::endl;
// Key 4
// Value 4 - экономим один вызов конструктора!
map.emplace(4, 4);
std::cout << std::endl;
// А теперь добавим элемент, который уже есть
// Key 3
// Value 3 - элемент уже есть, но мы его все равно создадим
map.emplace(3, 3);
std::cout << std::endl;
// Key 3 - элемент создастся, только если такого еще нет
const auto& [it, inserted] = map.try_emplace(3, 3);
// Not inserted
if (!inserted) std::cout << "Not inserted" << std::endl;
return 0;
}
👍7
Как определить, начинается ли строка с подстроки?
Если у вас есть C++20, то используем
Если же его нет, то можно использовать compare, который возвращает 0 если (под) строки равны:
Менее читаемое, чем первый вариант, но как-будто бы терпимо.
Если же вы хотите напрячь мозги своего коллеги-джуна, то используйте std::string::rfind, передав 0 в качестве позиции:
Давайте прочитаем доку: rfind ищет *последнюю* подстроку, которая совпадает с переданной последовательностью символов. Когда передан параметр pos (второй), то поиск включает символы которые начинаются в или до позиции pos, игнорируя все возможные совпадения начинающиеся после pos. Таким образом, передав 0 как второй аргумент, мы ищем строку, которая начинается не позже 0, то есть в начале. Обычный же find продолжит искать подстроки далее начала.
Если у вас есть C++20, то используем
starts_with и радуемся краткому и понятному коду.Если же его нет, то можно использовать compare, который возвращает 0 если (под) строки равны:
std::string prefix("Who");
if (str.compare(0, prefix.size(), prefix) == 0)Менее читаемое, чем первый вариант, но как-будто бы терпимо.
Если же вы хотите напрячь мозги своего коллеги-джуна, то используйте std::string::rfind, передав 0 в качестве позиции:
std::string str = "Who let the dogs out";
if (s.rfind("Who", 0) == 0) {
Давайте прочитаем доку: rfind ищет *последнюю* подстроку, которая совпадает с переданной последовательностью символов. Когда передан параметр pos (второй), то поиск включает символы которые начинаются в или до позиции pos, игнорируя все возможные совпадения начинающиеся после pos. Таким образом, передав 0 как второй аргумент, мы ищем строку, которая начинается не позже 0, то есть в начале. Обычный же find продолжит искать подстроки далее начала.
👀3
Рассмотрим следующий код, в котором определен конструктор копирования для производного класса:
Как видим, мы ожидали вывод
Так будет
Важно понимать, что конструктор копирования базового класса скопирует только члены базового класса, иначе говоря, если написать
То же самое верно и для конструктора перемещения наследника:
#include <iostream>
class Base
{
public:
Base() : bValue(3) {}
int bValue;
};
class Derived : public Base
{
public:
Derived() = default;
Derived(const Derived& other) : dValue(other.dValue) {}
int dValue = 0;
};
int main() {
Derived d1;
d1.dValue = 4;
d1.bValue = 7;
Derived d2(d1); // делаем копию
std::cout << d2.dValue << " " << d2.bValue << std::endl; // 4 3
}
Как видим, мы ожидали вывод
4 7, а получили 4 3, то есть конструктор копирования не скопировал значение из базового класса, а вызвал его конструктор по-умолчанию. Это ожидаемое поведение, ведь мы "забыли" *явно* вызвать базовый конструктор копирования. Исправим ситуацию:Derived(const Derived& other) : Base(other), dValue(other.dValue) {}Так будет
4 7Важно понимать, что конструктор копирования базового класса скопирует только члены базового класса, иначе говоря, если написать
Derived(const Derived& other) : Base(other) {}, то вывод будет неверным: 0 7То же самое верно и для конструктора перемещения наследника:
Derived (Derived && d) : Base (std::move(d)) {/* init Derived vars */}👍4👀3
Если не указать атрибут наследования, то для производного класса объявленного как class будет приватное наследование, а для struct - публичное:
#include <iostream>
#include <map>
class Base
{
public:
void f() {}
};
class Derived1 : Base // не указан атрибут наследования
{
};
struct Derived2 : Base // не указан атрибут наследования
{
};
int main()
{
Derived1 d1;
Derived2 d2;
d1.f(); // Ошибка, private inheritance
d2.f(); // OK, public inheritance
return 0;
}
🫡6
Начиная с С++11 параметр типа шаблона можно сделать дружественным, пример:
Класс Bob стал другом класса Alice<Bob> и получил доступ до его приватных членов.
#include <iostream>
template <typename T>
class Alice
{
friend T; // не friend class T
int priv_int = 4;
};
class Bob
{
public:
void print(const Alice<Bob>& alice) {
std::cout << alice.priv_int // Bob is friend of Alice
<< std::endl;
}
};
int main()
{
Alice<Bob> a;
Bob b;
b.print(a); // 4
return 0;
}
Класс Bob стал другом класса Alice<Bob> и получил доступ до его приватных членов.
🔥8
Можно ли вызывать виртуальные функции из конструкторов и деструкторов? Можно, но поведение может быть неожиданным (и такого лучше избегать). Пример:
Как видим при вызове конструтора производного класса Derived сперва вызовется конструктор базового. При этом пока выполняется конструктор базового класса, производная часть объекта остается неинициализированной. Поэтому конструктору базового класса при вызове виртуального метода было бы опасно вызывать переопределенную версию производного, так как такой метод мог бы обратиться к объектам производного класса. Поэтому C++ при создании (и удалении) объекта рассматривает его тип как изменяющийся. При вызове вртуального метода из базового конструктора вызовется метод базового класса, а при вызове из производного - этого производного. После окончания вызова конструктора базового класса вызовется конструктор производного.
При вызове деструктора будет похожая ситуация, но в обратном порядке. При вызове деструктора базового класса производная часть уже будет удалена то есть уже будет вызыван деструктор производного класса. При этом виртуальный метод в базовом деструкторе будет вызван для базового типа.
#include <iostream>
class Base
{
public:
Base() {
std::cout << "Base::Base" << std::endl;
f1(); // всегда вызов Base::f1, даже если он переопределен в наследнике
}
virtual ~Base() {
std::cout << "Base::~Base" << std::endl;
f2(); // всегда вызов Base::f2, даже если он переопределен в наследнике
};
virtual void f1() { std::cout << "Base::f1" << std::endl; }
virtual void f2() { std::cout << "Base::f2" << std::endl; }
};
class Derived : public Base
{
public:
Derived() {
std::cout << "Derived::Derived" << std::endl;
f1();
}
~Derived() override {
std::cout << "Derived::~Derived" << std::endl;
f2();
};
void f1() override { std::cout << "Derived::f1" << std::endl; }
void f2() override { std::cout << "Derived::f2" << std::endl; }
};
int main() {
Derived d;
std::cout << "============" << std::endl;
d.f2();
std::cout << "************" << std::endl;
}
// Output:
// Base::Base
// Base::f1
// Derived::Derived
// Derived::f1
// ============
// Derived::f2
// ************
// Derived::~Derived
// Derived::f2
// Base::~Base
// Base::f2
Как видим при вызове конструтора производного класса Derived сперва вызовется конструктор базового. При этом пока выполняется конструктор базового класса, производная часть объекта остается неинициализированной. Поэтому конструктору базового класса при вызове виртуального метода было бы опасно вызывать переопределенную версию производного, так как такой метод мог бы обратиться к объектам производного класса. Поэтому C++ при создании (и удалении) объекта рассматривает его тип как изменяющийся. При вызове вртуального метода из базового конструктора вызовется метод базового класса, а при вызове из производного - этого производного. После окончания вызова конструктора базового класса вызовется конструктор производного.
При вызове деструктора будет похожая ситуация, но в обратном порядке. При вызове деструктора базового класса производная часть уже будет удалена то есть уже будет вызыван деструктор производного класса. При этом виртуальный метод в базовом деструкторе будет вызван для базового типа.
👍4
У шаблона класса могут быть статические члены, при этом у каждого экземпляра для любого конкретного типа будет свой экземпляр:
Как видим у экземляра Class<float> свои экземпляры для статических членов size() и sz, а у Class<int> свои, отличные от них.
#include <iostream>
template <typename T>
class Class
{
public:
static int size() {
return sz;
}
static int sz;
T value;
};
template <typename T>
int Class<T>::sz = 0; // дефолтное значение для всех экземпляров шаблонов
int main() {
Class<float> fc1, fc2, fc3;
Class<int> ic;
fc1.sz = 3;
ic.sz = 4;
std::cout << fc1.size() << " " << fc2.size()
<< " " << Class<float>::size(); // 3 3 3
std::cout << std::endl;
std::cout << ic.size(); // 4
}
Как видим у экземляра Class<float> свои экземпляры для статических членов size() и sz, а у Class<int> свои, отличные от них.
❤🔥6
Давайте набросаем простой класс, который поможет отследить вызов конструктора и деструктора:
Далее мы просто создадим временное значение Xray:
Как видим, деструктор вызовется в той же строке кода, что и конструктор, то есть до вывода "Wait a sec", то есть до закрываюшей скобки.
Теперь же создадим ссылку (констанную lvalue) на временный объект:
Получили другой и возможно ожидаемый для первого случая вывод.
В чем же дело? Виноват механизм продления времени жизни временных объектов, который звучит так:
Прочитать подробнее можно в источнике.
struct Xray
{
Xray(std::string value)
: mValue(std::move(value))
{
std::cout << "Xray ctor, value is " << mValue << std::endl;
}
Xray(const Xray& other)
: mValue(other.mValue)
{
std::cout << "Xray const& ctor, value is " << mValue << std::endl;
}
~Xray()
{
std::cout << "~Xray dtor, value is " << mValue << std::endl;
}
std::string mValue;
};
Далее мы просто создадим временное значение Xray:
void main()
{
// Вывод: Xray ctor, value is 1
// Вывод: Xray dtor, value is 1
Xray{"1"};
std::cout << "Wait a sec" << std::endl;
}
Как видим, деструктор вызовется в той же строке кода, что и конструктор, то есть до вывода "Wait a sec", то есть до закрываюшей скобки.
Теперь же создадим ссылку (констанную lvalue) на временный объект:
void main()
{
// Вывод: Xray ctor, value is 1
const Xray& xrayRef = Xray{"1"};
// Вывод: xrayRef value is 1
std::cout << "xrayRef value is " << xrayRef.mValue << std::endl;
} // Вывод: Xray dtor, value is 1
Получили другой и возможно ожидаемый для первого случая вывод.
В чем же дело? Виноват механизм продления времени жизни временных объектов, который звучит так:
Если вы приняли временный объект по константной lvalue ссылке или rvalue ссылкe, то его время жизни будет продлено до времени жизни ссылки.
Прочитать подробнее можно в источнике.
PVS-Studio
Продление жизни временных значений в С++: рецепты и подводные камни
Прочитав эту статью, вы узнаете следующее: способы, которыми можно продлить время жизни временного объекта в С++; рекомендации и подводные камни этого механизма, с которыми может столкнуться С...
👍3
Прохожу курс, там сказано сравните время вставки в конец вектора и дека миллиона и 5 млн случайных чисел. Напишем код:
После запуска на 5 разных платформах получилось, что дек быстрее на 3, на одной примерно также, а на последней (MSVC) медленнее. По мнению авторов курса, дек быстрее, тк там нет копирования при ресайзе (reserve для вектора сказано не использовать).
Лично у меня два вывода: 1) берем вектор и не паримся, он все равно очень быстрый 2) такие вопросы без окружения в тестирование выносить - дурной тон.
#include <vector>
#include <deque>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <chrono>
#include <string>
void fill_random_vector(size_t size)
{
std::vector<int> v;
for (size_t i = 0; i < size; ++i) {
v.push_back(rand());
}
std::cout << "v[" << size - 1 << "] = " << v[size - 1] << std::endl;
}
void fill_random_deque(size_t size)
{
std::deque<int> d;
for (size_t i = 0; i < size; ++i) {
d.push_back(rand());
}
std::cout << "d[" << size - 1 << "] = " << d[size - 1] << std::endl;
}
template<typename F>
void measure_time(F func, const std::string& text)
{
auto begin = std::chrono::steady_clock::now();
func();
auto end = std::chrono::steady_clock::now();
std::cout << text << " "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count()
<< " ms" << std::endl;
}
int main()
{
srand((unsigned)time(0));
measure_time([](){ fill_random_vector(1'000'000); }, "fill_random_vector 1'000'000");
measure_time([](){ fill_random_deque(1'000'000); }, "fill_random_deque 1'000'000");
measure_time([](){ fill_random_vector(5'000'000); }, "fill_random_vector 5'000'000");
measure_time([](){ fill_random_deque(5'000'000); }, "fill_random_deque 5'000'000");
return 0;
}
После запуска на 5 разных платформах получилось, что дек быстрее на 3, на одной примерно также, а на последней (MSVC) медленнее. По мнению авторов курса, дек быстрее, тк там нет копирования при ресайзе (reserve для вектора сказано не использовать).
Лично у меня два вывода: 1) берем вектор и не паримся, он все равно очень быстрый 2) такие вопросы без окружения в тестирование выносить - дурной тон.
👍4