Ответ 1
Компилятор должен вести себя как - если копия произошла от vector
до вызова Foo
.
Если компилятор может доказать, что существует допустимое поведение абстрактной машины без видимых побочных эффектов (в рамках поведения абстрактной машины, а не на реальном компьютере!), который включает перемещение std::vector
в Foo
, он может сделайте это.
В приведенном выше случае это (перемещение не имеет видимых побочных эффектов абстрактной машины) истинно; компилятор, возможно, не сможет это доказать.
Возможно, наблюдаемое поведение при копировании std::vector<T>
:
- Вызов конструкторов копирования для элементов. Делать это с помощью
int
нельзя. - Вызов по умолчанию
std::allocator<>
в разное время. Это вызывает::new
и::delete
(возможно, 1). В любом случае::new
и::delete
не были заменены в вышеуказанной программе, поэтому вы не можете наблюдать это под стандартом. - Вызов деструктора
T
больше раз на разные объекты. Не наблюдается с помощьюint
. -
vector
не является пустым после вызоваFoo
. Никто не рассматривает это, поэтому он пуст, как если бы он не был. - Ссылки или указатели или итераторы к элементам внешнего вектора отличаются от внешних. Никакие ссылки, векторы или указатели не переносятся в элементы вектора вне
Foo
.
Пока вы можете сказать "но что, если система потеряла память, а вектор большой, разве это не наблюдаемо?":
Абстрактная машина не имеет условия "из памяти", она просто имеет распределение иногда с ошибкой (бросание std::bad_alloc
) по не ограниченным причинам. Это не приводит к действительным действиям абстрактной машины, и не может быть не вызвано не распределением (фактической) памяти (на фактическом компьютере), поскольку несуществование памяти не имеет наблюдаемых побочных эффектов.
Немного больше игрового случая:
int main() {
int* x = new int[std::size_t(-1)];
delete[] x;
}
в то время как эта программа явно выделяет слишком много памяти, компилятор может свободно ничего не выделять.
Мы можем идти дальше. Равномерно:
int main() {
int* x = new int[std::size_t(-1)];
x[std::size_t(-2)] = 2;
std::cout << x[std::size_t(-2)] << '\n';
delete[] x;
}
можно преобразовать в std::cout << 2 << '\n';
. Этот большой буфер должен существовать абстрактно, но до тех пор, пока ваша "настоящая" программа ведет себя как-будто бы абстрактная машина, на самом деле не должна ее выделять.
К сожалению, делать это в любом разумном масштабе сложно. Есть много и много способов утечки информации из программы на С++. Поэтому, полагаясь на такие оптимизации (даже если они произойдут), не будет хорошо заканчиваться.
1 Было несколько вещей, связанных с объединением вызовов new
, которые могут смутить проблему, я не уверен, что было бы законно пропускать вызовы, даже если был заменен ::new
.
Важным фактом является то, что существуют ситуации, когда компилятор не должен вести себя как-если бы была копия, даже если std::move
не был вызван.
Когда вы return
, локальная переменная из функции в строке, которая выглядит как return X;
и X
, является идентификатором, а эта локальная переменная имеет продолжительность автоматического хранения (в стеке), операция неявно перемещение, и компилятор (если он может) может исключить существование возвращаемого значения и локальной переменной в один объект (и даже опустить move
).
То же самое верно, когда вы строите объект из временного - операция неявно перемещается (поскольку она привязана к rvalue) и может полностью отойти от перемещения.
В обоих случаях компилятор должен рассматривать это как перемещение (а не копию), и он может ускорить ход.
std::vector<int> foo() {
std::vector<int> x = {1,2,3,4};
return x;
}
что X
не имеет std::move
, но он перемещается в возвращаемое значение, и эта операция может быть отменена (X
, а возвращаемое значение может быть превращено в один объект).
Это:
std::vector<int> foo() {
std::vector<int> x = {1,2,3,4};
return std::move(x);
}
блокирует elision, как это делает:
std::vector<int> foo(std::vector<int> x) {
return x;
}
и мы можем даже заблокировать ход:
std::vector<int> foo() {
std::vector<int> x = {1,2,3,4};
return (std::vector<int> const&)x;
}
или даже:
std::vector<int> foo() {
std::vector<int> x = {1,2,3,4};
return 0,x;
}
поскольку правила неявного перемещения преднамеренно хрупки. (0,x
- использование сильно заклятого оператора ,
).
Теперь, полагаясь на неявное перемещение, не возникающее в таких случаях, как этот последний ,
, не рекомендуется: стандартный комитет уже изменил случай с неявной копией на неявный-перемещение, поскольку неявный-перемещение было добавлено в потому что они считали это безобидным (где функция возвращает тип A
с A(B&&)
ctor, а оператор return return b;
, где b
имеет тип b
; в выпуске С++ 11, который сделал копия, теперь она делает ход.) Нельзя исключить дальнейшее расширение неявного перемещения: явно использовать const&
, вероятно, самый надежный способ предотвратить его сейчас и в будущем.