Std::vector, построение по умолчанию, С++ 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 копируются из него.

Теперь, с С++ 11, есть ссылки rvalue. Поэтому он не может работать так. Если a vector построено как

std::vector<S> v(42);

то, скорее всего, реализации предпочтут по умолчанию построить объекты n внутри вектора, так как построение копии может быть недоступно. Это было бы изменением в этом случае.

Мой вопрос:

  • Указывает ли стандарт С++ 03, что std::vector должен иметь конструктор, определенный выше, т.е. с аргументом по умолчанию? В частности, есть ли гарантия того, что записи векторного объекта будут скопированы вместо построенного по умолчанию?
  • Что говорит стандарт С++ 11 об этой же точке?
  • Я рассматриваю это как возможность для разрыва между С++ 03 и C + 11. Рассмотрена ли эта проблема? Решено?

PS: Пожалуйста, никаких комментариев о конструкторе по умолчанию для класса S выше. Это было или реализовано в какой-то форме ленивой конструкции.

Ответ 1

Предоставляет ли стандартное условие С++ 03, что std::vector должен иметь конструктор, определенный выше, т.е. с аргументом по умолчанию? В частности, есть ли гарантия того, что записи векторного объекта будут скопированы, а не построены по умолчанию?

Да, указанное поведение состоит в том, что x копируется n раз, чтобы инициализировать контейнер, содержащий элементы n, которые являются всеми копиями x.


Что говорит стандарт С++ 11 об этой же точке?

В С++ 11 этот конструктор был превращен в два конструктора.

vector(size_type n, const T& x, const Allocator& = Allocator()); // (1)
explicit vector(size_type n);                                    // (2)

За исключением того факта, что у него больше нет аргумента по умолчанию для второго параметра, (1) работает так же, как в С++ 03: x копируется n раз.

Вместо аргумента по умолчанию для x добавлен (2). Это значение конструктора инициализирует элементы n в контейнере. Копии не копируются.

Если вам требуется старое поведение, вы можете гарантировать, что (1) вызывается путем предоставления второго аргумента вызову конструктора:

std::vector<S> v(42, S());

Я рассматриваю это как возможность для разрыва между С++ 03 и С++ 11. Я рассматриваю это как возможность для разрыва между С++ 03 и С++ 11. Рассмотрена ли эта проблема? Решено?

Да, как показывает ваш пример, это действительно потрясающее изменение.

Поскольку я не являюсь членом комитета по стандартизации С++ (и я не уделял особо пристального внимания библиотечным материалам в рассылках), я не знаю, в какой степени обсуждалось это нарушение.

Ответ 2

Я думаю, что решение для используемого вами варианта использования не является оптимальным и не полным, поэтому у вас возникли проблемы с обновлением до С++ 11.

С++ всегда заботится о семантике, и когда вы пишете программу на С++, вам лучше понять вашу семантику. Поэтому в вашем случае вы хотите создать N объектов, но пока вы их не меняете, вы хотите, чтобы они использовали одну и ту же память для оптимизации. Хорошая идея, но как это сделать: 1) конструктор копирования. 2) статическая реализация + конструктор копирования. Вы рассматривали оба решения?

Предположим, вам нужны M-векторы из N объектов, сколько раз разделяемая память будет выделена, если вы выберете 1-й сценарий? Это M, но зачем нам выделять память M раз, если мы хотим создать векторы, содержащие объекты MxN?

Таким образом, правильная реализация заключается в том, чтобы указывать на статическую память по умолчанию и выделять память только при изменении объекта. В таком случае выделение M векторов из N объектов даст вам... 1 'разделяемое' распределение памяти.

В вашем случае вы нарушили правильный семантический злоупотребляющий конструктор копирования, который: 1) не очевидно 2) не является оптимальным и теперь вам нужно окупиться.