2014-11-04 2 views
1

В книге The C++ Programming Language приведен пример использования операций по умолчанию. Мой вопрос фокусируется на операции перемещения по умолчанию.По умолчанию движение строительства

struct S { 
    std::string a; 
    int b; 
}; 

S f(S arg) { 
    S s0{}; 
    S s1(s0); //s1 {s0}; in the book 
    s1 = arg; 
    return s1; 
} 

После этого он говорит:

Копия строительство s1 копий s0.a и s0.b. Возврат s1 перемещает s1.a и s1.b, оставляя s1.a в качестве пустой строки и s1.b без изменений. Обратите внимание, что значение перемещенного объекта встроенного типа не изменяется. Это самая простая и самая быстрая задача для компилятора.

Я думаю, что это означает, что, если я пишу:

S s3{"tool",42}; 
f(s3); 

Поскольку значение s1 перемещается, s1.a будет вернуться к «» и s1.b не меняется? Затем, когда f() завершает выполнение, s1 будет уничтожен? Я пытаюсь найти способ проверить свою догадку, но я не могу найти способ узнать значения s1 после выполнения функции, потому что это, конечно, локально. Я написал деструктор только для того, чтобы найти ценности, прежде чем они будут уничтожены.

~S() { 
    std::cout << a << " " << b << '\n'; 
} 

Выходы:

0 //values of s0? 
tool 42 
tool 42 
tool 42 

Кажется, я думаю, совершенно неправильно, и я совершенно не понимаю текст. Может ли кто-нибудь объяснить текст в ясности цитаты?

+1

Вы можете видеть оптимизации малого строки (которая изменит путь перемещенных-из строки выглядит так), IIRC, 'станд :: string' по-прежнему не соответствует требованиям GCC 4.9. –

ответ

2

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

S(const S&) = default; 
S(S&&) = default; 
S& operator=(const S&) = default; 
S& operator=(S&&) = default; // not required here but should be added for completeness 

Тогда, во всяком случае, RVO вступает в игру. Как отмечалось в других ответах, компилятору разрешено исключать вызовы для копирования и перемещения конструкторов. В GCC и clang вы можете отключить это, добавив опцию компилятора -fno-elide-constructors. После этого вы получите этот результат:

42 // moved s1 (this can theoretically be different, because the value of s1.a is unspecified) 
0 // s0 
tool 42 // arg 
tool 42 // return value of f() 
tool 42 // s3 

Demo

+0

Хорошо, мне интересно узнать о порядке уничтожения. Я предполагаю, что деструкторы начинают вызываться, когда f() завершает работу. Кажется, что первый из них был уничтожен? Почему это? – morbidCode

+0

@recursivePointer, потому что он создан последним в области 'f()'. Возвращаемое значение 'f()' и 's3' уничтожается позже, поскольку они находятся в области функции вызова. –

2

s1.a вернется в ""

Maybe. Из спецификации конструктора перемещения он «остается в допустимом состоянии с неопределенным значением». Как правило, если строка использует динамически выделенный массив, и обе строки используют совместимые распределители, тогда перемещение передаст право собственности на массив, оставив старую строку пустым. Но если строка использует «короткую оптимизацию строк», где короткие короткие строки хранятся внутри самого объекта string, чтобы сохранить стоимость распределения динамической памяти, тогда было бы быстрее оставить прежнюю строку неизменной.

s1.b и неизменна

Да. Примитивные типы никогда не изменяются при назначении.

Затем, когда f() заканчивает выполнение, s1 будет уничтожен?

Возможно, нет. Оптимизация движения elision (иногда называемая «оптимизация возвращаемого значения»), вероятно, используется здесь, если у вас нет очень примитивного компилятора или намеренно отключите оптимизацию. s1 будет создан в стеке стека вызывающего абонента, так что для его возврата не требуется никакой работы.

Я не могу найти способ, чтобы знать значение s1 после выполнения функции

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

S s2 = std::move(s1); 

и исследовать s1 впоследствии. Или вы могли бы написать свой собственный шаг конструктор

S(S && other) : a(std::move(other.a)), b(b) { 
    std::cout << other.a << '\n'; 
} 

, но, как было отмечено выше, это, вероятно, не будет использоваться для возвращаемого значения функции.

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