使用联合作为树中的项类型,以便存储两种不同的数据类型

Use a union as a item-type in a tree in order to store two different data types

本文关键字:存储 两种 数据类型 类型      更新时间:2023-10-16

请考虑以下代码:

union tree_item
{
    tree_item(std::valarray<double> const& point)
        : point(point)
    { }
    tree_item(unsigned coord, double coord_value)
        : splitting_line({ coord, coord_value })
    { }
    struct {
        unsigned coord;
        double coord_value;
    } splitting_line;
    std::valarray<double> point;
};

我们如何确保,如果一个tree_item对象存储了一个point, point的析构函数被调用?我可以写

~tree_item() {
    point.~valarray();
}

但是我认为,如果我们给coordcoord_value赋值,这会导致未定义的行为。

我想做的就是用tree_item代替

中的T
template<typename T>
struct tree_node
{
    tree_node(T const& item)
        : item(item)
    { }
    tree_node(T&& item)
        : item(std::move(item))
    { }
    bool is_leaf() const noexcept {
        return !left && !right;
    }
    T item;
    std::shared_ptr<tree_node<T>> left, right;
};

splitting_linepoint是我想要存储在树中的两种类型的项。显然,我可以分别为splitting_linepoint编写基类和派生类。然而,由于在这种情况下,我不想对这些项执行操作(我只想存储这些不同的类型),并且我需要使用dynamic_cast或某种类型标志,这将使我的代码变得过于复杂。

那么,我该怎么办呢?

不应该使用union,而应该使用一个类来处理拥有真正变体类型所需的更多功能:boost::variant:

using tree_item = boost::variant<
    splitting_line,
    std::valarray<double>
};

variant对象将确保根据存储的内容调用正确的析构函数。它还跟踪它所拥有的对象,这样你就可以对它执行类型安全的操作(这在你的union上也是不可能的)。

这就是虚拟继承的作用。使用一个通用的基类型,可能称为tree_item_base,并带有两个派生类型tree_item_pointtree_item_splitting_line。但是,tree_node需要使用引用或自动指针类型。

如果你想在设计中使用不受限制的联合,为了调用正确的析构函数,你必须将联合内部的信息存储在其外部,例如在tree_node或其他类型的包装器类型中,这将在某些枚举中保留数据类型并调用正确的构造函数/析构函数。

我在gcc/clang上试过你的代码,稍微修改了一下,它实际上没有发出任何警告。

#include <functional>
#include <iostream>  
#include <valarray>
union tree_item
{
    tree_item(std::valarray<double> const& ppoint) : point(ppoint) { 
        //new (&point)std::valarray<double>(ppoint);
    }
    tree_item(unsigned coord, double coord_value)
        : splitting_line({ coord, coord_value })
    { }
    ~tree_item(){
      // cannot call point dtor here because we dont know if it was used
    }
    struct {
        unsigned coord;
        double coord_value;
    } splitting_line;
    std::valarray<double> point;
};
int main() {
    std::valarray<double> point={1,2,3};
    // constructed with std::valarray, destructor must be called manually
    tree_item nn(point);
    std::cout << nn.point[2];
    nn.point.~valarray<double>();
    tree_item nn2(1.0, 2.0);
    return 0;
}
http://melpon.org/wandbox/permlink/02f5GI5WTTlLvprY