Семантика перемещения и вставка размещения - это две функции, привнесенные контейнерами C++11, которые могут оказать очень положительное влияние на ваши приложения C++. Повышаю. Контейнер реализует обе технологии как для компиляторов C++11, так и C++03.
Все контейнеры, предлагаемые Boost.Container, могут хранить только подвижные типы и фактические требования для value_type
зависят от каждой операции с контейнером. Следуя требованиям C++11 даже для компиляторов C++03, многие операции теперь требуют подвижных или конструируемых по умолчанию типов вместо просто копируемых конструируемых типов.
Сами контейнеры также являются подвижными, с гарантией отсутствия броска, если операции с выделением или предикатом (если присутствуют) не являются броском. Это позволяет выполнять высокопроизводительные операции при передаче данных между векторами. Давайте посмотрим пример:
#include <boost/container/vector.hpp>
#include <boost/move/utility_core.hpp>
#include <cassert>
class non_copyable
{
BOOST_MOVABLE_BUT_NOT_COPYABLE(non_copyable)
public:
non_copyable(){}
non_copyable(BOOST_RV_REF(non_copyable)) {}
non_copyable& operator=(BOOST_RV_REF(non_copyable)) { return *this; }
};
int main ()
{
using namespace boost::container;
vector<non_copyable> v;
non_copyable nc;
v.push_back(boost::move(nc));
assert(v.size() == 1);
v.reserve(100);
assert(v.capacity() >= 100);
v.resize(200);
assert(v.size() == 200);
vector<non_copyable> v_other(boost::move(v));
assert(v_other.size() == 200);
assert(v.empty());
return 0;
}
Все контейнеры, предлагаемые Boost.Container, реализуют вставку размещения, что означает, что объекты могут быть встроены непосредственно в контейнер из аргументов пользователя без создания какого-либо временного объекта. Для компиляторов без вариадных шаблонов поддержка размещения вставки эмулируется до конечного (10) числа аргументов.
Дорогие для перемещения типы являются идеальными кандидатами для размещения функций и в случае контейнеров узлов ( список
, набор
, ...) место позволяет хранить неподвижные и некопируемые типы в контейнерах! Давайте посмотрим пример:
#include <boost/container/list.hpp>
#include <cassert>
class non_copy_movable
{
non_copy_movable(const non_copy_movable &);
non_copy_movable& operator=(const non_copy_movable &);
public:
non_copy_movable(int = 0) {}
};
int main ()
{
using namespace boost::container;
list<non_copy_movable> l;
non_copy_movable ncm;
l.emplace(l.begin(), 0);
assert(l.size() == 1);
l.emplace(l.begin());
assert(l.size() == 2);
return 0;
}
Неполные типы позволяют стирать типы и рекурсивные типы данных, а программисты C и C++ годами используют его для построения сложных структур данных, таких как структуры деревьев, где узел может иметь произвольное количество детей.
Как насчет стандартных контейнеров? Контейнеры неполных типов уже давно обсуждаются, как объясняется в замечательной статье Мэтта Остерна (Стандартный библиотекарь: Контейнеры неполных типов):
“Unlike most of my columns, this one is about something you
can't do with the C++ Standard library: put incomplete types in one of the
standard containers. This column explains why you might want to do this,
why the standardization committee banned it even though they knew it was
useful, and what you might be able to do to get around the restriction.”
“In 1997, shortly before the C++ Standard was completed,
the standardization committee received a query: Is it possible to create
standard containers with incomplete types? It took a while for the committee
to understand the question. What would such a thing even mean, and why on
earth would you ever want to do it? The committee eventually worked it out
and came up with an answer to the question. (Just so you don't have to skip
ahead to the end, the answer is "no.") But the question is much
more interesting than the answer: it points to a useful, and insufficiently
discussed, programming technique. The standard library doesn't directly support
that technique, but the two can be made to coexist.”
“In a future revision of C++, it might make sense to relax
the restriction on instantiating standard library templates with incomplete
types. Clearly, the general prohibition should stay in place - instantiating
templates with incomplete types is a delicate business, and there are too
many classes in the standard library where it would make no sense. But perhaps
it should be relaxed on a case-by-case basis, and vector
looks like a good candidate for such special-case treatment: it's the one
standard container class where there are good reasons to instantiate it with
an incomplete type and where Standard Library implementors want to make it
work. As of today, in fact, implementors would have to go out of their way
to prohibit it!”
Стандарт C++11 также осторожно относится к неполным типам и STL: “17.6.4.8 Другие функции (...) 2. последствия не определены в следующих случаях: (...) В частности, если неполный тип (3.9) используется в качестве аргумента шаблона при инстанцировании компонента шаблона, если специально не разрешено для этого компонента ”.
К счастью, все контейнеры Boost.Container, за исключением static_vector
и basic_string
, предназначены для поддержки неполных типов. static_vector
является специальным, поскольку он статически выделяет память для value_type
и для этого требуются полные типы и basic_string
реализует оптимизацию малых струн, которая также требует полных типов.
Boost.Container containers supporting incomplete
types also support instantiating iterators to those incomplete elements.
Для определения рекурсивных контейнеров можно использовать большинство контейнеров Boost.Container:
#include <boost/container/vector.hpp>
#include <boost/container/stable_vector.hpp>
#include <boost/container/deque.hpp>
#include <boost/container/list.hpp>
#include <boost/container/map.hpp>
#include <boost/container/string.hpp>
using namespace boost::container;
struct data
{
int i_;
vector<data> v_;
vector<data>::iterator vi_;
stable_vector<data> sv_;
stable_vector<data>::iterator svi_;
deque<data> d_;
deque<data>::iterator di_;
list<data> l_;
list<data>::iterator li_;
map<data, data> m_;
map<data, data>::iterator mi_;
friend bool operator <(const data &l, const data &r)
{ return l.i_ < r.i_; }
};
struct tree_node
{
string name;
string value;
list<tree_node> children_;
list<tree_node>::iterator selected_child_;
};
int main()
{
stable_vector<data> sv;
sv.resize(100);
tree_node root;
root.name = "root";
root.value = "root_value";
root.children_.resize(7);
root.selected_child_ = root.children_.begin();
return 0;
}
Контейнеры неполных типов полезны для разрушения зависимостей файла заголовка и улучшения типов компиляции. С Бустом. Контейнер, вы можете написать файл заголовка, определяющий класс с контейнерами неполных типов в качестве членов данных, если вы тщательно поместите все детали реализации, которые требуют знания размера value_type
в файл реализации:
В этом файле заголовка мы определяем класс (MyClassHolder)
, который содержит вектор
неполного типа (MyClass
), который объявлен только вперед.
#include <boost/container/vector.hpp>
class MyClass;
class MyClassHolder
{
public:
void AddNewObject(const MyClass &o);
const MyClass & GetLastObject() const;
private:
::boost::container::vector<MyClass> vector_;
};
Затем мы можем определить MyClass
в собственном файле заголовка.
class MyClass
{
private:
int value_;
public:
MyClass(int val = 0) : value_(val){}
friend bool operator==(const MyClass &l, const MyClass &r)
{ return l.value_ == r.value_; }
};
And include it only in the implementation file of MyClassHolder
#include "MyClassHolder.h"
#include "MyClass.h"
void MyClassHolder::AddNewObject(const MyClass &o)
{ vector_.push_back(o); }
const MyClass & MyClassHolder::GetLastObject() const
{ return vector_.back(); }
Наконец, мы можем просто собрать, связать и запустить!
#include "MyClassHolder.h"
#include "MyClass.h"
#include <cassert>
int main()
{
MyClass mc(7);
MyClassHolder myclassholder;
myclassholder.AddNewObject(mc);
return myclassholder.GetLastObject() == mc ? 0 : 1;
}
В статье N2913, озаглавленной SCARY Iterator Assignment and Initialization, предложено требование о том, что типы итераторов стандартного контейнера не зависят от какого-либо аргумента типа, кроме типа контейнера value_type
, difference_type
, pointer
и const_pointer
. В частности, согласно предложению, типы итераторов стандартного контейнера не должны зависеть от типов контейнера key_compare
, hasher
, key_equal
или allocator
.
Этот документ продемонстрировал, что операции SCARY имеют решающее значение для эффективного внедрения общих шаблонов проектирования с использованием компонентов STL. Он показал, что реализации, поддерживающие операции SCARY, уменьшают раздувание объектного кода, устраняя избыточные специализации итераторов и шаблонов алгоритмов.
Boost.Container containers implement SCARY
iterators so the iterator type of a container is only dependent on the allocator_traits<allocator_type>::pointer
type (the pointer type of the
value_type
to be inserted
in the container). Reference types and all other typedefs are deduced from
the pointer type using the C++11 pointer_traits
utility. This leads to lower code bloat in algorithms and classes templated
on iterators.