Несколько головоломок из различных докладов, постов в блогах и прочего.
Оглавление
- Головоломка 1: Получение unique_ptr по ссылке
- Головоломка 2: Захват временного по ссылке
- Головоломка 3: ошибки при использовании стандартных функций
- Головоломка 4: Выбрасывание мертвых
Головоломка 1: Получение unique_ptr по ссылке
unique_ptr<A> myFun()
{
unique_ptr<A> pa(new A());
return pa;
}
const A& rA = *myFun();
Этот код компилируется, но rA
содержит мусор. Почему этот код недействителен?
Примечание. если мы назначаем возврат myFun
именованной переменной unique_ptr
перед ее разыменованием, все работает нормально.
Ответ:
unique_ptr
передаст право собственности другому unique_ptr
, но в этом коде нет ничего, что могло бы захватить право собственности на возвращаемый указатель. Другими словами, он не может передать право собственности, поэтому он будет уничтожен. Правильный способ:
unique_ptr<A> rA = myFun(); // Pass the ownership
or
const A rA = *myFun(); // Store the values before destruction
В этом коде возвращаемый указатель будет уничтожен, а ссылка ссылается на объект, который разрушается вскоре после того, как использование этой ссылки вызывает неопределенное поведение.
Головоломка 2: Захват временного по ссылке
vector<bool> vb{true, true, false, true}; auto proxy = vb[0]; std::cout << proxy // ok, Prints true vb.reserve(100); // A: ??? std::cout << proxy // Error!! likely to print false on clang, why?
Отвечать:
Строка A аннулирует прокси, поэтому proxy
был аннулирован vb.reserve.
Возьмите пример ниже —
void example1_3() { string_view s; // s points to null string name = "abcdefghijklmnop"; s = name; // A: s points to {name'} i.e. data owned by name’ cout << s[0]; // B: ok – s[0] is ok because {a} is alive name = "frobozz"; // C: name modified => name’ is invalid cout << s[0]; // D: error – because it contains {invalid} }unique_ptr<A> myFun() { unique_ptr<A> pa(new A()); return pa; } const A& rA = *myFun();
вот почему руководство говорит -
Никогда не используйте ссылку в случае временного аргумента.
Возьмите пример ниже —
char& c = std::string{"hello my pretty long string"}[0]; cout << c; // (X) wrong to initialize a // reference ‘c’ with an invalid pointer, pointer // was invalidated with temporary string was // destroyed.
Головоломка 3: ошибки при использовании стандартных функций
int main() { auto x=10, y=2; auto& good = min(x,y); // ok, {x,y} cout << good; // ok, fine. auto& bad = min(x,y+1) cout << bad; // ERROR, why?? }
Отвечать:
int main() { auto x=10, y=2; auto& good = min(x,y); // ok, {x,y} cout << good; // ok, fine. auto& bad = min(x,y+1) // A: IN: {x, temp(y+1)} // OUT: temp2 obtained by {x,temp} // min() returns temp2 // temp destroyed hence → temp2 = {invalid} cout << bad; // ERROR, bad initialized as invalid now }
В обычном C++ этот код компилируется, но имеет неопределенное поведение.
Примечание. На практике на трех основных компиляторах (GCC, VC++, clang) этот код не дает сбоев и выглядит
работа. Это потому, что одним из проявлений «неопределенного поведения» может быть «случается делать то, что вы ожидаете».
Тем не менее, это не определено, поэтому следует быть осторожным.
Головоломка 4: Выбрасывание мертвых
// godbolt.org/z/p_QjCR static int gi = 0; void f() { int i = 0; throw &i; // ERROR, why?? throw &gi; // OK }
Отвечать:
В отличие от возврата, тип брошенного объекта не может быть передан через сигнатуры функций. Поэтому не выбрасывайте Pointer с временем жизни, отличным от статического.