nanoflann kdtree适配器引发了段错误

时间:2018-02-13 06:39:16

标签: c++ flann

我试图在我的一类中使用nanoflann的kdtree结构。我有一个kdtree适配器的以下标头,它只是this的一个小的(可能是坏的)变体,应该允许带有3d向量的kd树结构作为元素:

// "KdVec3dAdaptor.h"
#include "nanoflann.hpp"
#include <vector>


using Distance = nanoflann::metric_L2;  // Was in template before, dont need it, this way its easier to read
using VectorOfVectorsType = std::vector<std::array<double,3>>;

template <class VectorOfVectorsType>
struct KdVec3dAdaptor
{
    typedef KdVec3dAdaptor<VectorOfVectorsType> self_t;
    typedef typename Distance::template traits<double,self_t>::distance_t metric_t;
    typedef nanoflann::KDTreeSingleIndexAdaptor< metric_t,self_t,3,size_t>  index_t;

    VectorOfVectorsType m_data; 
    index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index.

    KdVec3dAdaptor() = default;

    KdVec3dAdaptor(const VectorOfVectorsType &mat, const int leaf_max_size = 10) : m_data(mat)
    {
        const size_t dims = mat[0].size();
        index = new index_t( dims, *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) );
        index->buildIndex();
    }

    ~KdVec3dAdaptor()
    { // destructor
        delete index;
    }

    KdVec3dAdaptor& operator=(KdVec3dAdaptor&& other)
    {  // move assignment operator
        if (this != &other)
        {  // check that no self-assignment is performed
            delete index;
            index = other.index;
            m_data = other.m_data;
            other.index = nullptr;
        }
        return *this;
    }

    const self_t & derived() const
    {
        return *this;
    }
    self_t & derived()
    {
        return *this;
    }

    inline size_t kdtree_get_point_count() const
    {// Must return the number of data points
        return m_data.size();
    }

    inline double kdtree_get_pt(const size_t idx, int dim) const
    { // Returns the dim'th component of the idx'th point in the class
        return m_data[idx][dim];
    }

    template <class BBOX>
    bool kdtree_get_bbox(BBOX & /*bb*/) const
    { // return false to default to a standard bbox computation loop.
        return false;
    }
};

我面临的问题是将此kdtree结构用作类的成员变量。具有所需类型定义的该类的最小版本将是:

// "test.cpp"
#include <iostream>
#include <array>
#include "KdVec3dAdaptor.h"

const int dimens = 3;
using vec3d = std::array<double, dimens>;
using vec_arr =  std::vector<vec3d>;
using my_kd_tree =  KdVec3dAdaptor<vec_arr>;

class foo
{
public:
    const int n;
    const double level;
    my_kd_tree init_ps_tree;

    foo(const int &N, const double &level, vec_arr &kset)
    :n(N), level(level)
    {
        init_ps_tree = my_kd_tree(kset,10);
    }
};

树是在类的不同方法中声明的(我使用另一个类型为vec_arr的类变量组成树点)。现在这里是我在追踪错误时迷失的地方。当我尝试调用以下方法时:

void test()
{
    double level = -0.3;
    std::vector<double> arr1 {0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1};
    std::vector<double> arr2 {0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1};
    vec3d vec1 {{1.0, 0.0, 0.0}};
    vec3d vec2 {{1.0, 0.0, 0.0}};
    vec3d vec3 {{1.0, 0.0, 0.0}};
    vec3d vec4 {{1.0, 0.0, 0.0}};
    vec3d vec5 {{1.0, 0.0, 0.0}};
    vec3d vec6 {{1.0, 0.0, 0.0}};
    vec3d vec7 {{1.0, 0.0, 0.0}};
    vec3d vec8 {{1.0, 0.0, 0.0}};
    vec_arr vecarr1 {vec1, vec2, vec3, vec4, vec5, vec6, vec7, vec8};

    int N = 5;
    vec_arr  init_points;

    foo entity(N, level, kset);
    std::cout << entity.n << std::endl;
}

我得到一个分段错误,我认为必须归因于kdtree结构的标头。但是,我无法进一步追踪它,因为:

  • 在main方法中执行完全相同的操作非常正常,只有在其他地方完成时才会出现错误
  • 没有访问任何类实例,代码编译得很好
  • 删除任何未使用的vec3d变量可以解决问题(正如您可以猜到的那样,我在以后的某个方法中需要它们)
  • 即使有更多成员变量,有时从类中删除其他成员变量(如leveln)也可以解决问题
  • 使用new创建实例不会改变任何内容
  • 删除类中的任何成员变量可以解决问题(但我实际上需要它们在项目的其他地方)

我在俯瞰什么? KdVec3dAdaptor中的析构函数有问题吗?我敢肯定它一定是愚蠢的。提前感谢您的任何帮助和建议。

1 个答案:

答案 0 :(得分:2)

您正在删除未创建的对象。看看KdVec3dAdaptor类的析构函数。在test函数中,您创建了foo对象

foo entity(N, level);
此构造函数中的

my_kd_tree init_ps_tree;成员是使用KdVec3dAdaptor的默认构造函数创建的,该构造函数未设置index成员。当test函数结束时,将调用foo析构函数并销毁init_ps_tree,但未设置index,并且

delete index;
KdVec3dAdaptor析构函数中的

崩溃了你的程序。在KdVec3dAdaptor的默认ctor中,您应将index成员设置为0.

修改

我不知道为什么要将默认构造函数的定义保留为

KdVec3dAdaptor() = default;

在进入foo ctor的主体之前看看foo构造函数的定义

{
    init_ps_tree = my_kd_tree(kset,10);   // [1] init_ps_tree was already created
}

对象init_ps_tree是使用默认构造函数创建的(index成员可能包含垃圾数据 - 随机值,如果此对象创建为本地变量)。在这一行[1]中调用移动赋值运算符,并在行下面执行

delete index;  // very dangerous

你可以在空指针上调用delete,但在你的情况下,我们不知道index的值是什么。 首先,将KdVec3dAdaptor的ctor写为

KdVec3dAdaptor::KdVec3dAdaptor() : index(0) {}

接下来,在你的移动赋值运算符中,你应该使用std :: move来移动矢量数据,现在可以复制矢量。

    if (this != &other)
    {  // check that no self-assignment is performed
        delete index;
        index = other.index;
        m_data = std::move(other.m_data); // [2] this vector can be moved
        other.index = nullptr;
    }