Несколько головоломок из различных докладов, постов в блогах и прочего.

Оглавление

  1. Головоломка 1: Получение unique_ptr по ссылке
  2. Головоломка 2: Захват временного по ссылке
  3. Головоломка 3: ошибки при использовании стандартных функций
  4. Головоломка 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 example
unique_ptr<A> myFun()
{
    unique_ptr<A> pa(new A());
    return pa;
}

const A& rA = *myFun();
1_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} }

вот почему руководство говорит -

Никогда не используйте ссылку в случае временного аргумента.

Возьмите пример ниже —

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 с временем жизни, отличным от статического.

использованная литература

  1. https://stackoverflow.com/questions/30858850/dereferencing-a-temporary-unique-ptr
  2. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1179r0.pdf