2015-02-20 2 views
4

В любом случае, похоже, что стандартная библиотека должна ссылаться на конструкторы копирования вместо перемещения конструкторов, когда конструктор перемещения не является лишним (false).Noexcept and copy, move constructors

Теперь я не понимаю, почему это так. И все больше Visual Studio VC v140 и gcc v 4.9.2, похоже, делают это по-другому.

Я не понимаю, почему это не вызывает беспокойства, например. вектор. Я имею в виду, как должен vector :: resize() быть в состоянии дать надежную гарантию исключения, если T нет. Как я вижу, уровень исключения для вектора будет зависеть от T. Независимо от того, используется ли копирование или перемещение. Я понимаю, что не нужно просто подмигивать компилятору для оптимизации обработки исключений.

Эта небольшая программа вызывает конструктор копирования при компиляции с помощью gcc и перемещением конструктора при компиляции с помощью Visual Studio.

include <iostream> 
#include <vector> 

struct foo { 
    foo() {} 
    // foo(const foo &) noexcept { std::cout << "copy\n"; } 
    // foo(foo &&) noexcept { std::cout << "move\n"; } 
    foo(const foo &) { std::cout << "copy\n"; } 
    foo(foo &&) { std::cout << "move\n"; } 

    ~foo() noexcept {} 
}; 

int main() { 
    std::vector<foo> v; 
    for (int i = 0; i < 3; ++i) v.emplace_back(); 
} 
+0

путем копирования элементов вектора во время перераспределения вы оставите прежнее хранилище неповрежденным; перемещая их, он изменяется. если выбрано исключение, это нельзя отменить, поскольку может быть выбрано другое исключение –

ответ

10

Это многогранный вопрос, так что несите меня, пока я просматриваю различные аспекты.

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

Рассмотрим конструктора копирования для класса Foo:

Foo(const Foo& o); 

Если этот конструктор бросков, основная гарантия исключения дает вам следующие знания:

  • Нет нового объект не был создан. Если конструктор выбрасывает, объект не создается.
  • o не был изменен. Его единственное участие здесь - это ссылка на const, поэтому его нельзя изменять. Другие случаи подпадают под заголовок «жуткий действие на расстоянии», или, возможно, «фундаментальный языковой инвариант».
  • Ресурсов не было утечено, программа в целом по-прежнему согласована.

В конструктор перемещения:

Foo(Foo&& o); 

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

Далее, посмотрите на vector::resize. Его реализация, как правило, по той же схеме:

void vector<T, A>::resize(std::size_t newSize) { 
    if (newSize == size()) return; 
    if (newSize < size()) makeSmaller(newSize); 
    else if (newSize <= capacity()) makeBiggerSimple(newSize); 
    else makeBiggerComplicated(newSize); 
} 
void vector<T, A>::makeBiggerComplicated(std::size_t newSize) { 
    auto newMemory = allocateNewMemory(newSize); 
    constructAdditionalElements(newMemory, size(), newSize); 
    transferExistingElements(newMemory); 
    replaceInternalBuffer(newMemory, newSize); 
} 

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

Но если мы используем перемещение вместо этого, это не сработает. После перемещения одного объекта любое последующее исключение означает, что исходный буфер был изменен. И поскольку у нас нет гарантии, что движущиеся объекты обратно тоже не бросаются, мы даже не можем восстановиться. Таким образом, чтобы сохранить сильную гарантию, мы должны потребовать, чтобы операция перемещения не делала никаких исключений. Если у нас есть это, мы в порядке. И именно поэтому у нас есть move_if_noexcept.

Что касается разницы между MSVC и GCC: MSVC поддерживает только noexcept с версии 14, и поскольку он все еще находится в разработке, я подозреваю, что стандартная библиотека не была обновлена, чтобы воспользоваться еще.

+0

Большое спасибо за этот ответ. Я думаю, это объясняет это очень хорошо. – user2099460

9

Основная проблема заключается в том, что невозможно создать надежную защиту исключений с помощью конструктора перемещения метания. Представьте, если в векторном изменении размера на полпути через перемещение элементов в новый буфер, создается конструктор перемещения. Как вы могли восстановить предыдущее состояние? Вы не можете использовать конструктор перемещения снова, потому что, ну, это может просто продолжаться.

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

Это принципиально невозможно предложить сильно Exception безопасного изменения размеров() с метательным движением, но легко с метанием копьями. Этот фундаментальный факт отражается повсеместно по сравнению со стандартной библиотекой.

GCC и VS относятся к этому по-разному, потому что они находятся на разных этапах соответствия. VS оставил noexcept одной из последних функций, которые они реализуют, поэтому их поведение является своего рода промежуточным поведением между C++ 03 и C++ 11/14. В частности, поскольку у них нет возможности определить, действительно ли ваш конструктор перемещения noexcept или нет, они просто должны угадать. Из памяти они просто предполагают, что это noexcept, потому что бросание конструкторов перемещения не является обычным явлением, и неспособность к переходу будет критической проблемой.

+0

И большое спасибо за этот ответ. Это также объясняет это очень хорошо. – user2099460

Смежные вопросы