полиморфный вызов во время выполнения чисто виртуальной функции через std :: reference_wrapper, ведущий себя непоследовательно - PullRequest
0 голосов
/ 01 марта 2019

Я представляю вам эту загадку кода:

Используя этот компилятор:

user @ bruh: ~ / test $ g ++ --version

g ++ (Ubuntu 7.3.0-16ubuntu3) 7.3.0 Copyright (C) 2017 Free Software Foundation, Inc. Это бесплатное программное обеспечение;см. источник для условий копирования.Там нет гарантии;даже не для MERCHANTABILITY или FITNESS ДЛЯ ОСОБЕННОЙ ЦЕЛИ.

... и эта строка компиляции:

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?

  2. Почему это segfault , когда пользователь указал аргумент> 1?

Мое краткое объяснение того, что делает код:

Создает много объектов, полученных из абстрактного базового класса.Хранит ссылки на объекты в контейнерах как std :: reference_wrapper вокруг ссылки на абстрактный базовый класс.Создает контейнеры std :: reference_wrapper немного по-другому.Вызывает производное переопределение чисто виртуальной функции через std :: reference_wrappers.Segfaults конкретно в случае, обозначенном в исходном коде выше.

Я умоляю экспертов C ++ ... Пожалуйста, помогите мне!Это увлекательно, и я понятия не имею, почему это происходит!Я, наверное, сделал что-то глупое.

Ответы [ 2 ]

0 голосов
/ 01 марта 2019

Фактически, ваш код делает это: он хранит ссылку на временные данные в векторе (инкапсулированные в reference_wrapper);эта ссылка сразу становится висячей, как только элемент управления выходит из тела цикла, сразу после его нажатия;затем вы извлекаете эту висячую ссылку и вызываете для нее виртуальную функцию-член, заставляя вашу программу вести себя неопределенно;и как только вы зажигаете UB, это похоже на точку сингулярности, где все рациональные объяснения прекращаются.

Кстати, управление в конце концов выйдет из функции, достигнув конца тела, даже если вы не введете туда return.0_о

0 голосов
/ 01 марта 2019

Вы создаете висячую ссылку в этом фрагменте кода:

  for( unsigned int i = 0; i < n_elements; i++ ) {

    v.emplace_back( i ); // [1]

    base_vector_0.emplace_back( v[ i ] ); // [2]
  }

[1] добавьте новый элемент, [2] сохраните ссылку на этот элемент.Если vector был перестроен при вызове 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 безопасен.

...