通过std :: reference_wrapper行为不一致的纯虚拟函数的运行时多态调用

时间:2019-03-01 06:32:37

标签: c++ c++11 std reference-wrapper run-time-polymorphism

我向您展示此代码之谜:

使用此编译器:

  

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

     

g ++(Ubuntu 7.3.0-16ubuntu3)7.3.0   版权所有(C)2017自由软件基金会,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. 当用户提供的参数= 1时,为什么此不是段错误

  2. 当用户提供参数> 1时为什么会出现 segfault

我对代码功能的简短说明:

创建许多派生自抽象基类的对象。围绕抽象基类引用将对对象的引用作为std :: reference_wrapper存储在容器中。创建std :: reference_wrapper的容器略有不同。通过std :: reference_wrappers调用纯虚拟函数的派生重写。特别是在上面的源代码中指出的情况下出现段错误。

我恳请C ++专家...请帮助我!这真是令人着迷,我不知道为什么会这样!我可能做过一些愚蠢的事。

2 个答案:

答案 0 :(得分:1)

您可以在以下代码段中创建悬空引用:

  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访问这些引用是安全的。

答案 1 :(得分:0)

实际上,您的代码是这样做的:它在向量中存储了对临时变量的引用(封装在reference_wrapper中);在控件退出循环主体后,立即将其推入后立即悬空;接下来,您检索该悬挂的引用并在其上调用一个虚拟成员函数,从而迫使您的程序行为不确定。一旦您点燃了UB,就好比一个奇点,所有理性的解释都停止了。

顺便说一句,即使您未在其中插入return,控制也会在到达其主体末端时最终退出该函数。 0_o