Полиморфный вызов во время выполнения чисто виртуальной функции через std::reference_wrapper, ведущий себя непоследовательно
Я представляю вам эту загадку кода:
Используя этот компилятор:
user @ bruh: ~ / test $ g ++ --version
g ++ (Ubuntu 7.3.0-16ubuntu3) 7.3.0 Copyright (C) 2017 Free Software Foundation, Inc. Это бесплатное программное обеспечение; см. источник для условий копирования. Там нет гарантии; даже не для ИЗДЕЛИИ или ФИТНЕСА ДЛЯ ОСОБЕННОЙ ЦЕЛИ.
... и эта строка компиляции:
g ++ main.cpp class.cpp -o main -g
... и эти файлы:
class.hpp:
class base {
public:
base();
virtual void f() = 0;
};
class derived : public base {
public:
derived( unsigned int id );
void f() override;
unsigned int id;
};
class.cpp:
#include <iostream>
#include "class.hpp"
base::base() {
return;
}
derived::derived( unsigned int id )
:
id( id ),
base() {
return;
}
void derived::f() {
std::cout << "Ahoy, Captain! I am " << id << std::endl;
return;
}
main.cpp:
#include <iostream>
#include <functional>
#include <vector>
#include "class.hpp"
int main() {
unsigned int n_elements;
std::cout << "enter the number of elements: ";
std::cin >> n_elements;
std::cout << std::endl;
std::vector< class derived > v;
std::vector< std::reference_wrapper< class base > > base_vector_0;
std::vector< std::reference_wrapper< class base > > base_vector_1;
for( unsigned int i = 0; i < n_elements; i++ ) {
v.emplace_back( i );
base_vector_0.emplace_back( v[ i ] );
}
for( unsigned int i = 0; i < n_elements; i++ ) {
base_vector_1.emplace_back( v[ i ] );
}
std::cout << "sanity check:" << std::endl;
for( unsigned int i = 0; i < n_elements; i++ ) {
class base &base = v[ i ];
base.f();
}
std::cout << "case 1:" << std::endl;
for( unsigned int i = 0; i < n_elements; i++ ) {
base_vector_1[ i ].get().f();
}
std::cout << "case 0:" << std::endl;
for( unsigned int i = 0; i < n_elements; i++ ) {
base_vector_0[ i ].get().f();
}
return 0;
}
... я получаю следующий вывод:
user@bruh:~/test$ ./main
enter the number of elements: 1
sanity check:
Ahoy, Captain! I am 0
case 1:
Ahoy, Captain! I am 0
case 0:
Ahoy, Captain! I am 0
harrynh3@bruh:~/test$ ./main
enter the number of elements: 2
sanity check:
Ahoy, Captain! I am 0
Ahoy, Captain! I am 1
case 1:
Ahoy, Captain! I am 0
Ahoy, Captain! I am 1
case 0:
Segmentation fault (core dumped)
Мои вопросы:
Почему это не происходит, если пользователь указал аргумент = 1?
Почему возникает этот segfault, когда пользователь указал аргумент> 1?
Мое краткое объяснение того, что делает код:
Создает много объектов, полученных из абстрактного базового класса. Хранит ссылки на объекты в контейнерах как std::reference_wrapper вокруг ссылки на абстрактный базовый класс. Создает контейнеры std::reference_wrapper немного по-другому. Вызывает производное переопределение чисто виртуальной функции через std::reference_wrappers. Сегфолты конкретно в случае, обозначенном в исходном коде выше.
Я умоляю экспертов C++... Пожалуйста, помогите мне! Это увлекательно, и я понятия не имею, почему это происходит! Я, наверное, сделал что-то глупое.
2 ответа
Вы создаете висячую ссылку в этом фрагменте кода:
for( unsigned int i = 0; i < n_elements; i++ ) {
v.emplace_back( i ); // [1]
base_vector_0.emplace_back( v[ i ] ); // [2]
}
[1] добавить новый элемент, [2] сохранить ссылку на этот элемент. Если вектор был перестроен во время вызова emplace_back
все ссылки недействительны, и вы ссылаетесь на элемент, который не существует. vector
восстанавливается, когда его текущая емкость превышается путем добавления в него новых предметов.
Если вы хотите хранить именно n_elements
в v
вектор и избежать перестройки вектор, который вы можете назвать reserve
:
std::vector< class derived > v;
v.reserve(n_elements); // added
std::vector< std::reference_wrapper< class base > > base_vector_0;
std::vector< std::reference_wrapper< class base > > base_vector_1;
for( unsigned int i = 0; i < n_elements; i++ ) {
v.emplace_back( i );
base_vector_0.emplace_back( v[ i ] );
}
сейчас, когда emplace_back
не называются никакие ссылки недействительными, и доступ к этим ссылкам base_vector_0
безопасно.
Фактически, ваш код делает это: он хранит ссылку на временные данные в векторе (инкапсулирован в reference_wrapper
); эта ссылка сразу становится висячей, как только элемент управления выходит из тела цикла, сразу после его нажатия; затем вы извлекаете эту висячую ссылку и вызываете для нее виртуальную функцию-член, заставляя вашу программу вести себя неопределенно; и как только вы зажигаете UB, это похоже на точку сингулярности, где все рациональные объяснения прекращаются.
Кстати, управление в конечном итоге выйдет из функции, как только достигнет конца тела, даже если вы не положите return
там. 0_о