std::vector, конструкция по умолчанию, C++11 и критические изменения
Сегодня я столкнулся с довольно тонкой проблемой, о которой я хотел бы узнать ваше мнение.
Рассмотрим следующий класс с общими именами сада:
struct S
{
S() : p_impl(new impl) {}
private:
struct impl;
boost::shared_ptr<impl> p_impl;
};
Веселье появляется, когда вы пытаетесь поместить их в векторы следующим образом:
std::vector<S> v(42);
Теперь, по крайней мере, с MSVC 8, все элементы в v
делить то же самое impl
член. На самом деле, что вызывает это vector
конструктор:
template <typename T, typename A = ...>
class vector
{
vector(size_t n, const T& x = T(), const A& a = A());
...
};
Под кулисами только один S
объект строится по умолчанию, n
элементы vector
скопированы с него.
Теперь, с C++11, есть ссылки на значения. Так что это не может работать так. Если vector
построен как
std::vector<S> v(42);
тогда, скорее всего, реализации выберут по умолчанию построить n
объекты внутри вектора, так как конструкция копирования может быть недоступна. Это было бы серьезным изменением в этом случае.
Мой вопрос:
- Соответствует ли стандарт C++03 тому, что
std::vector
должен иметь конструктор, определенный как выше, т.е. с аргументом по умолчанию? В частности, есть ли гарантия, что записи векторного объекта будут скопированы, а не созданы по умолчанию? - Что стандарт C++ 11 говорит об этом же пункте?
- Я вижу в этом возможность переломного изменения между C++03 и C+11. Была ли исследована эта проблема? Решено?
PS: Пожалуйста, никаких комментариев о конструкторе класса по умолчанию S
выше. Это было это или осуществление некоторой формы ленивой конструкции.
2 ответа
Соответствует ли стандарт C++03 тому, что
std::vector
должен иметь конструктор, определенный выше, то есть с аргументом по умолчанию? В частности, есть ли гарантия, что записи векторного объекта будут скопированы, а не созданы по умолчанию?
Да, указанное поведение таково x
копируется n
раз, так что контейнер инициализируется, чтобы содержать с n
элементы, которые все являются копиями x
,
Что говорит C++11 Standard об этом же пункте?
В C++ 11 этот конструктор был превращен в два конструктора.
vector(size_type n, const T& x, const Allocator& = Allocator()); // (1)
explicit vector(size_type n); // (2)
За исключением того факта, что он больше не имеет аргумента по умолчанию для второго параметра, (1) работает так же, как в C++03: x
копируется n
раз.
Вместо аргумента по умолчанию для x
(2) был добавлен. Этот конструктор инициализирует значение n
элементы в контейнере. Копии не сделаны.
Если вам требуется старое поведение, вы можете убедиться, что (1) вызывается, предоставив второй аргумент для вызова конструктора:
std::vector<S> v(42, S());
Я вижу в этом возможность переломного изменения между C++03 и C++11. Я вижу в этом возможность переломного изменения между C++03 и C++11. Была ли исследована эта проблема? Решено?
Да, как показывает ваш пример, это действительно серьезное изменение.
Поскольку я не являюсь членом комитета по стандартизации C++ (и я не обращал особо пристального внимания на связанные с библиотекой статьи в рассылках), я не знаю, в какой степени это серьезное изменение обсуждалось.
Я думаю, что решение для описанного вами варианта использования не является оптимальным и неполным, поэтому у вас возникли проблемы при обновлении до C++ 11.
C++ всегда заботится о семантике, и когда вы пишете программу на C++, вам лучше понять свою семантику. Так что в вашем случае вы хотите создать N объектов, но пока вы не меняете их, вы хотите, чтобы они совместно использовали одну и ту же память для оптимизации. Хорошая идея, но как это сделать: 1) Копировать конструктор. 2) статическая реализация + конструктор копирования. Рассматривали ли вы оба решения?
Предположим, вам нужно M векторов из N объектов. Сколько раз будет выделяться общая память, если вы выберете 1-й сценарий? Это M, но зачем нам выделять память M раз, если мы хотим создать векторы, содержащие объекты MxN?
Поэтому правильной реализацией здесь является указание на статическую память по умолчанию и выделение памяти только в случае изменения объекта. В таком случае выделение M векторов из N объектов даст вам... 1 "общее" выделение памяти.
В вашем случае вы нарушили правильный семантический злоупотребляющий конструктор копирования, который: 1) неочевиден 2) не оптимален и теперь вы должны окупиться.