【问题标题】:runtime polymorphic invocation of pure virtual function via std::reference_wrapper behaving inconsistently通过 std::reference_wrapper 行为不一致的纯虚函数的运行时多态调用
【发布时间】:2019-03-01 06:32:36
【问题描述】:

我向你展示这个密码谜题:

使用这个编译器:

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

g++ (Ubuntu 7.3.0-16ubuntu3) 7.3.0 版权所有 (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. 当用户提供的参数 = 1 时,为什么这不是段错误

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

我对代码作用的简短解释:

创建许多从抽象基类派生的对象。将容器中对象的引用存储为围绕抽象基类引用的 std::reference_wrapper。创建 std::reference_wrapper 的容器略有不同。通过 std::reference_wrappers 调用纯虚函数的派生覆盖。 Segfaults 特别是在上面源代码中表示的情况下。

我恳求 C++ 专家...请帮助我!这很有趣,我不知道为什么会这样!我可能做了一些愚蠢的事情。

【问题讨论】:

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


    【解决方案1】:

    您在这段代码的 sn-p 中创建悬空引用:

      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 在超过其当前容量时通过添加新项目来重建。

    如果您想在v 向量中准确存储n_elements 并避免重建向量,您可以调用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 访问这些引用是安全的。

    【讨论】:

    【解决方案2】:

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

    顺便说一句,即使您没有将return 放在那里,控件最终也会在到达其主体端时退出函数。 0_o

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-23
      • 2012-03-23
      • 2021-01-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多