Указывает ли стандарт С++ спецификацию реализации STL для компилятора?

При написании ответа на вопрос this я столкнулся с интересной ситуацией - вопрос демонстрирует сценарий, когда нужно было бы поместить класс в контейнер STL, но не удалось сделайте это из-за отсутствующего оператора конструктора/перемещения конструктора/назначения. В этом конкретном случае ошибка срабатывает std::vector::resize. Я сделал быстрый фрагмент в качестве решения и увидел еще один ответ, который предоставил конструктор перемещения вместо оператора присваивания и конструктора копирования, как и у меня. Было то, что другой ответ не компилировался в VS 2012, в то время как clang/gcc были довольны обоими подходами.

Во-первых:

// Clang and gcc are happy with this one, VS 2012 is not
#include <memory>
#include <vector>

class FooImpl {};

class Foo
{
    std::unique_ptr<FooImpl> myImpl;
public:
    Foo( Foo&& f ) : myImpl( std::move( f.myImpl ) ) {}
    Foo(){}
    ~Foo(){}
};

int main() {
    std::vector<Foo> testVec;
    testVec.resize(10);
    return 0;
}

Во-вторых:

// Clang/gcc/VS2012 are all happy with this
#include <memory>
#include <vector>

using namespace std;
class FooImpl {};

class Foo
{
    unique_ptr<FooImpl> myImpl;
public:
    Foo()
    {
    }
    ~Foo()
    {
    }
    Foo(const Foo& foo)
    {
        // What to do with the pointer?
    }
    Foo& operator= (const Foo& foo)
    {
        if (this != &foo)
        {
            // What to do with the pointer?
        }
        return *this;
    }
};

int main(int argc, char** argv)
{
    vector<Foo> testVec;
    testVec.resize(10);
    return 0;
}

Чтобы понять, что происходит, я просмотрел источники STL в VS 2012 и увидел, что он действительно вызывает оператор присваивания перемещения, поэтому мой образец работал (у меня нет машины Linux, доступной для понимания того, что происходит on в clang/gcc), а другой нет, поскольку у него был только конструктор копии перемещения.

Итак, это создало следующий вопрос: может ли компилятор свободно решить, как реализовать STL-методы (в данном случае std::vector::resize), так как радикально разные реализации могут вызывать непереносимый код? Или это просто ошибка VS 2012?

Ответ 1

Прежде всего, поскольку С++ 11, std::vector<> может хранить неконвертируемые типы. (пример). Посмотрите cppreference.

До С++ 11 T должен быть скопирован, как вы знаете.

T должен удовлетворять требованиям CopyAssignable и CopyConstructible.

Однако в С++ 11 требования полностью изменены.

Требования, предъявляемые к элементам, зависят от фактических операций, выполняемых на контейнере. Как правило, требуется, чтобы тип элемента был полным типом и отвечал требованиям Erasable, но многие функции-члены предъявляют более строгие требования.

.. Erasable:

Тип T стирается из контейнера X, если задано

A тип распределителя, определенный как X::allocator_type

m значение типа A, полученное из X::get_allocator()

p указатель типа T*, подготовленный контейнером

корректно формируется следующее выражение:

std::allocator_traits<A>::destroy(m, p);

И посмотрите на "Требования типа" std::vector:: изменить размер():

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

Таким образом, T не нужно копировать - ему нужно только уничтожить, перемещать и устанавливать по умолчанию конструкцию.

Кроме того, поскольку С++ 14, ограничение полного типа удаляется.

Требования, предъявляемые к элементам, зависят от фактических операций, выполняемых на контейнере. Как правило, требуется, чтобы тип элемента отвечал требованиям Erasable, но многие функции-члены предъявляют более строгие требования. Этот контейнер (но не его члены) может быть создан с неполным типом элемента, если распределитель удовлетворяет требованиям полноты распределителя.

Поэтому я считаю это из-за плохого стандартного соответствия VS2012. Он имеет некоторый дефект на последнем С++ (например, noexcept)


С++ 11 стандартная бумага N3337 говорит

изменение размера void (size_type sz);

E ff ects: Если sz <= size(), эквивалентно erase(begin() + sz, end());. Если size() < sz, добавляет sz - size() значения-инициализированные элементы последовательности.

Требуется: T должен быть CopyInsertable в * this.

Поэтому в строгом С++ 11 вы не можете использовать std::vector::resize() в этом случае. (вы можете использовать std::vector, хотя)

Однако это стандартный дефект и исправлен в С++ 14. и я думаю, что многие компиляторы хорошо работают с типами, не подлежащими копированию, потому что копирование не обязательно реализует std::vector::resize(). Хотя VS2012 не работает, это вызвано ошибкой VS2012 как @ComicSansMS, а не из-за самой std::vector::resize().

Ответ 2

Visual С++ 2012 не может автоматически сгенерировать конструктор перемещения и оператор назначения перемещения. Дефект, который будет исправлен только в предстоящей версии 2015 года.

Вы можете сделать свой первый пример компиляцией, добавив явный оператор назначения перемещения в Foo:

#include <memory>
#include <vector>

class FooImpl {};

class Foo
{
    std::unique_ptr<FooImpl> myImpl;
public:
    Foo( Foo&& f ) : myImpl( std::move( f.myImpl ) ) {}
    // this function was missing before:
    Foo& operator=( Foo&& f) { myImpl = std::move(f.myImpl); return *this; }
    Foo(){}
    ~Foo(){}
};

int main() {
    std::vector<Foo> testVec;
    testVec.resize(10);
    return 0;
}

Как подробно объясняется ikh answer, стандарт фактически не требует оператора присваивания переадресации. Соответствующие понятия для vector<T>::resize(): MoveInsertable и DefaultInsertable, который был бы встречен вашей начальной реализацией только с помощью конструктора перемещения.

Тот факт, что реализация VC также требует назначения перемещения здесь, представляет собой другой дефект, который уже был исправлен в VS2013.

Благодаря ikh и dyp за их проницательные вклады в этом вопросе.

Ответ 3

VS2012 - это компилятор С++ с некоторыми функциями С++ 11. Вызов этого компилятора С++ 11 немного растягивается.

Его стандартная библиотека очень С++ 03. Его поддержка семантики перемещения минимальна.

В VS2015 компилятор остается С++ 11 с некоторыми функциями С++ 11, но его поддержка семантики перемещения намного лучше.

VS2015 по-прежнему не имеет полной поддержки С++ 11 constexpr и имеет неполную поддержку SFINAE (то, что они называют "SFINAE" ) и некоторые сбои библиотеки. Он также имеет недостатки в нестатических инициализаторах элементов данных, списках инициализаторов, атрибутах, именах универсальных символов, некоторых данных concurrency, а его препроцессор несовместим. Это извлекается из собственного блога.

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

Все 3 компилятора имеют ошибки поверх функций, которые они поддерживают.

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

В этом случае был дефект в стандарте С++ 11. Отчеты об ошибках обычно фиксируются компиляторами и складываются в "компилятор" С++ 11 компиляторами, а также включаются в следующий стандарт. Дефект, о котором идет речь, был достаточно очевиден, что в основном все, кто фактически реализовал стандарт С++ 11, игнорировали дефект.


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

В то же время стандарт С++ оставляет много свободы. Тип итераторов в векторы С++ может быть необработанным указателем под стандартом или интеллектуальным индексом подсчета ссылок, который генерирует дополнительные ошибки при неправильном использовании или что-то еще. Компиляторы могут использовать эту свободу, чтобы их отладочные сборки были оснащены дополнительной проверкой ошибок (вылавливание поведения undefined для программистов) или использование этой свободы для различных приемов, которые могли бы обеспечить дополнительную производительность (вектор, который сохраняет свой размер и емкость в выделенный буфер может быть меньше для хранения, и обычно, когда вы запрашиваете размер/емкость, вы так или иначе получите доступ к данным.)

Ограничения, как правило, относятся к ограничениям времени жизни и сложности.

Обычно записывается какая-то эталонная реализация, анализируются ее ограничения и границы сложности, и они выдвигаются как ограничение. Иногда части остаются "свободными", чем требует эталонная реализация, что дает свободу компилятору или библиотекам.

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

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

Ответ 4

Стандарт С++ определяет ограничения на T для почти всех функций библиотеки библиотеки.

Например, в проекте n4296 ограничения для T для std::vector::resize, определенные в [vector.capacity]/13, равны.

Requires: T shall be MoveInsertable and DefaultInsertable into *this.

У меня нет доступа к окончательным стандартам для различных версий С++ для сравнения, но я бы предположил, что VS 2012 является несоответствующим в своей поддержке С++ 11 в этом примере.