检测 boos::variant 向量内的空指针

Posted

技术标签:

【中文标题】检测 boos::variant 向量内的空指针【英文标题】:Detecting null pointers inside vector of boos::variant 【发布时间】:2021-12-24 19:13:03 【问题描述】:

关注Heterogenous vectors of pointers. How to call functions中的问题。

我想知道如何识别 boost::variant 向量内的空点。

示例代码:

#include <boost/variant.hpp>
#include <vector>

template< typename T>
class A

    public:
        A()
        ~A()

        void write();

    private:
        T data;
;

template< typename T>
void A<T>::write()

    std::cout << data << std::endl;


class myVisitor
: public boost::static_visitor<>

    public:
        template< typename T>
        void operator() (A<T>* a) const
        
            a->write();
        
;

int main()

   A<int> one;
   A<double> two;

   typedef boost::variant<A<int>*, A<double>* > registry;
   std::vector<registry> v;

   v.push_back(&one);
   v.push_back(&two);

   A<int>* tst = new A<int>;

   for(auto x: v)
   
       boost::apply_visitor(myVisitor(), x);
       try delete tst; tst = nullptr;
       catch (...)
   


由于我正在删除指针,我希望最后一个指针会给我一个错误或其他东西。如何检查条目中的条目是否指向nullptr?

【问题讨论】:

deleteing nullptr 不是错误(它是无操作),但未使用 new 分配的 deleteing 指针是。更重要的是,它是未定义的行为,你不会 catch 任何异常。 你的意思是tst == nullptr 还是...? 在“现代”C++ 中,通常不需要在多态性之外使用指针。如果你需要指针,不要使用普通的非拥有指针,而是使用像std::unique_ptr这样的智能指针。 你能用boost::variant2::variant吗?它有boost::monostate。将其设为variant 中的第一个类型,以便之后检查它是否包含任何有用的内容 - 并且不要存储指针。请改用variant&lt;boost::monostate, A&lt;int&gt;, A&lt;double&gt; &gt; nullptrvariant 的关系如何?您有一个变体向量,并使用 nullptr 指向另一个不相关的指针...您忘记了 v.push_back(tst); 吗? 【参考方案1】:

注意:这部分忽略了这个问题的 X/Y,基于 tandom question (Heterogenous vectors of pointers. How to call functions)

您似乎追求的是多态集合,但不是虚拟类型层次结构。

这被称为类型擦除,Boost Type Erasure 使用Boost PolyCollection 方便地包装了这个用例。

类型擦除的变体可能看起来像any_collection:

Live On Coliru

#include <boost/variant.hpp>
#include <cmath>
#include <iostream>
#include <vector>

#include <boost/poly_collection/any_collection.hpp>
#include <boost/type_erasure/member.hpp>
namespace pc = boost::poly_collection;

BOOST_TYPE_ERASURE_MEMBER(has_write, write)
using writable = has_write<void()>;

template <typename T> class A 
  public:
    A(T value = 0) : data(value) 
    // A() = default; // rule of zero
    //~A() = default;

    void write() const  std::cout << data << std::endl; 

  private:
    T data/* = 0*/;
;

int main()

    pc::any_collection<writable> registry;
    A<int>    one(314);
    A<double> two(M_PI);

    registry.insert(one);
    registry.insert(two);

    for (auto& w : registry) 
        w.write();
    

打印

3.14159
314

请注意,插入顺序被保留,但迭代是逐个类型完成的。这也是使 PolyCollection 比不优化分配大小或使用指针的“常规”容器更高效的原因。

奖励:自然打印operator&lt;&lt;

使用经典的动态多态,如果不添加虚拟方法,这将无法工作,但使用 Boost TypeErasure ostreamable 是一个现成的概念:

Live On Coliru

#include <boost/variant.hpp>
#include <cmath>
#include <iostream>
#include <vector>

#include <boost/poly_collection/any_collection.hpp>
#include <boost/type_erasure/operators.hpp>
namespace pc = boost::poly_collection;

using writable = boost::type_erasure::ostreamable<>;

template <typename T> class A 
  public:
    A(T value = 0) : data(value) 
    // A() = default; // rule of zero
    //~A() = default;

  private:
    friend std::ostream& operator<<(std::ostream& os, A const& a) 
        return os << a.data;
    

    T data/* = 0*/;
;

int main()

    pc::any_collection<writable> registry;
    A<int>    one(314);
    A<double> two(M_PI);

    registry.insert(one);
    registry.insert(two);

    for (auto& w : registry) 
        std::cout << w << "\n";
    

打印和以前一样。

更新

到评论:

我想创建 n 个A&lt;someType&gt; 变量(这些是大对象)。所有这些变量都有一个写入函数,可以将内容写入文件。

我的想法是收集这些变量的所有指针,并在最后循环通过向量来调用每个写入函数。现在,我可能想分配内存并删除A&lt;someType&gt; 变量。如果发生这种情况,它不应该执行 write 函数。

这听起来像是shared_ptr 有意义的罕见情况之一,因为它允许您使用weak_ptr 观察对象的生命周期。

想象的对象图...

让我们发明一种节点类型,它可以参与一个非常大的对象图,这样您就可以保留指向它的某些节点的指针的“索引”。对于这个演示,我将把它做成一个树状结构图,我们将把References 保留在叶节点上:

using Object    = std::shared_ptr<struct INode>;
using Reference = std::weak_ptr<struct INode>;

现在,让我们在节点库中添加标识,这样我们就有了一种任意方式来标识要删除的节点(例如,所有具有奇数 id 的节点)。另外,任何节点都可以有子节点,所以我们也把它放在基节点中:

struct INode 
    virtual void write(std::ostream& os) const = 0;
    std::vector<Object> children;

    size_t id() const  return _id; 
  private:
    size_t _id = s_idgen++;
;

现在我们需要一些具体的派生节点类型:

template <typename> struct Node : INode 
    void write(std::ostream& os) const override;
;

using Root    = Node<struct root_tag>;
using Banana  = Node<struct banana_tag>;
using Pear    = Node<struct pear_tag>;
using Bicycle = Node<struct bicycle_tag>;
// etc

是的。想象力不是我的强项¯\(ツ)

生成随机数据

// generating demo data
#include <random>
#include <functional>
#include <array>
static std::mt19937                          s_prngstd::random_device();
static std::uniform_int_distribution<size_t> s_num_children(0, 3);

Object generate_object_graph(Object node, unsigned max_depth = 10) 
    std::array<std::function<Object()>, 3> factories = 
        []  return std::make_shared<Banana>(); ,
        []  return std::make_shared<Pear>(); ,
        []  return std::make_shared<Bicycle>(); ,
    ;

    for(auto n = s_num_children(s_prng); max_depth && n--;) 
        auto pick  = factories.at(s_prng() % factories.size());
        node->children.push_back(generate_object_graph(pick(), max_depth - 1));
    

    return node;

没什么特别的。只是一个随机生成的树,具有最大深度和节点类型的随机分布。

write to Pretty-Print

让我们添加一些逻辑来显示任何带有缩进的对象图:

// for demo output
#include <boost/core/demangle.hpp>

template <typename Tag> void Node<Tag>::write(std::ostream& os) const 
    os << boost::core::demangle(typeid(Tag*).name()) << "(id:" << id() << ") ";
    if (not children.empty()) 
        for (auto& ch : children) 
            ch->write(os << linebreak << "- " << indent);
            os << unindent;
        

        os << linebreak;
    
    os << "";

为了跟踪缩进级别,我将定义这些indent/unindent 修改流对象内的一些自定义状态的操纵器:

static auto s_indent = std::ios::xalloc();

std::ostream& indent(std::ostream& os)  return os.iword(s_indent) += 3, os; 
std::ostream& unindent(std::ostream& os)  return os.iword(s_indent) -= 3, os; 
std::ostream& linebreak(std::ostream& os) 
    return os << "\n" << std::setw(os.iword(s_indent)) << "";

应该可以。

获取叶节点

叶节点是没有任何子节点的节点。

这是一个深度优先的树访问器,采用任何输出迭代器:

template <typename Out>
Out get_leaf_nodes(Object const& tree, Out out) 
    if (tree)  
        if (tree->children.empty()) 
            *out++ = tree; // that's a leaf node!
         else 
            for (auto& ch : tree->children) 
                get_leaf_nodes(ch, out);
            
        
    
    return out;

删除一些节点:

又一个深度优先的访客:

template <typename Pred>
size_t remove_nodes_if(Object tree, Pred predicate)

    size_t n = 0;
    if (!tree)
        return n;

    auto& c = tree->children;

    // depth first
    for (auto& child : c)
        n += remove_nodes_if(child, predicate);

    auto e = std::remove_if(begin(c), end(c), predicate);
    n += std::distance(e, end(c));
    c.erase(e, end(c));

    return n;

演示时间

将它们结合在一起,我们可以打印一个随机生成的图表:

int main()

    auto root = generate_object_graph(std::make_shared<Root>());
    root->write(std::cout);

这会将其所有叶节点References 放入一个容器中:

    std::list<Reference> leafs;
    get_leaf_nodes(root, back_inserter(leafs));
    

我们可以使用他们的write() 方法打印:

    std::cout << "\nLeafs: " << leafs.size();
    for (Reference& ref : leafs)
        if (Object alive = ref.lock())
            alive->write(std::cout << " ");
            

当然,所有的叶子都还活着。但我们可以改变它!我们将按 id 删除五分之一的节点:

    auto _2mod5 = [](Object const& node)  return (2 == node->id() % 5); ;

    std::cout << "\nRemoved " << remove_nodes_if(root, _2mod5) << " 2mod5 nodes from graph\n";
    std::cout << "\n(Stale?) Leafs: " << leafs.size();
    

报告的叶子节点数看起来仍然相同。那是……不是 你想要什么。这就是您的问题所在:我们如何检测 被删除的节点?

    leafs.remove_if(std::mem_fn(&Reference::expired));
    std::cout << "\nLive leafs: " << leafs.size();
    

现在计数将准确反映剩余叶节点的数量。

Live On Coliru

#include <memory>
#include <vector>
#include <ostream>

using Object    = std::shared_ptr<struct INode>;
using Reference = std::weak_ptr<struct INode>;

static size_t s_idgen = 0;

struct INode 
    virtual void write(std::ostream& os) const = 0;
    std::vector<Object> children;

    size_t id() const  return _id; 
  private:
    size_t _id = s_idgen++;
;

template <typename> struct Node : INode 
    void write(std::ostream& os) const override;
;

using Root    = Node<struct root_tag>;
using Banana  = Node<struct banana_tag>;
using Pear    = Node<struct pear_tag>;
using Bicycle = Node<struct bicycle_tag>;
// etc

// for demo output
#include <boost/core/demangle.hpp>
#include <iostream>
#include <iomanip>

static auto s_indent = std::ios::xalloc();

std::ostream& indent(std::ostream& os)  return os.iword(s_indent) += 3, os; 
std::ostream& unindent(std::ostream& os)  return os.iword(s_indent) -= 3, os; 
std::ostream& linebreak(std::ostream& os) 
    return os << "\n" << std::setw(os.iword(s_indent)) << "";


template <typename Tag> void Node<Tag>::write(std::ostream& os) const 
    os << boost::core::demangle(typeid(Tag*).name()) << "(id:" << id() << ") ";
    if (not children.empty()) 
        for (auto& ch : children) 
            ch->write(os << linebreak << "- " << indent);
            os << unindent;
        

        os << linebreak;
    
    os << "";


// generating demo data
#include <random>
#include <functional>
#include <array>
static std::mt19937                          s_prngstd::random_device();
static std::uniform_int_distribution<size_t> s_num_children(0, 3);

Object generate_object_graph(Object node, unsigned max_depth = 10) 
    std::array<std::function<Object()>, 3> factories = 
        []  return std::make_shared<Banana>(); ,
        []  return std::make_shared<Pear>(); ,
        []  return std::make_shared<Bicycle>(); ,
    ;

    for(auto n = s_num_children(s_prng); max_depth && n--;) 
        auto pick  = factories.at(s_prng() % factories.size());
        node->children.push_back(generate_object_graph(pick(), max_depth - 1));
    

    return node;


template <typename Out>
Out get_leaf_nodes(Object const& tree, Out out) 
    if (tree)  
        if (tree->children.empty()) 
            *out++ = tree;
         else 
            for (auto& ch : tree->children) 
                get_leaf_nodes(ch, out);
            
        
    
    return out;


template <typename Pred>
size_t remove_nodes_if(Object tree, Pred predicate)

    size_t n = 0;
    if (!tree)
        return n;

    auto& c = tree->children;

    // depth first
    for (auto& child : c)
        n += remove_nodes_if(child, predicate);

    auto e = std::remove_if(begin(c), end(c), predicate);
    n += std::distance(e, end(c));
    c.erase(e, end(c));

    return n;


#include <list>
int main()

    auto root = generate_object_graph(std::make_shared<Root>());
    root->write(std::cout);

    std::list<Reference> leafs;
    get_leaf_nodes(root, back_inserter(leafs));

    std::cout << "\n------------"
              << "\nLeafs: " << leafs.size();
    for (Reference& ref : leafs)
        if (Object alive = ref.lock())
            alive->write(std::cout << " ");

    auto _2mod5 = [](Object const& node)  return (2 == node->id() % 5); ;

    std::cout << "\nRemoved " << remove_nodes_if(root, _2mod5) << " 2mod5 nodes from graph\n";
    std::cout << "\n(Stale?) Leafs: " << leafs.size();

    // some of them are not alive, see which are gone ("detecing the null pointers")
    leafs.remove_if(std::mem_fn(&Reference::expired));
    std::cout << "\nLive leafs: " << leafs.size();

打印例如

root_tag*(id:0) 
- bicycle_tag*(id:1) 
- bicycle_tag*(id:2) 
   - pear_tag*(id:3) 
   
- bicycle_tag*(id:4) 
   - bicycle_tag*(id:5) 
   - bicycle_tag*(id:6) 
   

------------
Leafs: 4 bicycle_tag*(id:1)  pear_tag*(id:3)  bicycle_tag*(id:5)  bicycle_tag*(id:6) 
Removed 1 2mod5 nodes from graph

(Stale?) Leafs: 4
Live leafs: 3

或查看COLIRU link 以获得更大的样本。

【讨论】:

您好,感谢您的回复。但是,它是否接受指向对象的指针?它可以检查它们是否没有指向 nullptr 吗? 那部分闻起来像X/Y problem,正如我所提到的。你需要指针做什么?正如其他人在您先前问题的答案中所指出的那样,使用带有指针的variant&lt;&gt; 99% 是无用的,而且可能不是您想要的。你能描述一下你真正想要解决的问题,而不是你认为它应该是什么样子吗? 是的,当然。我想创建 n 个A&lt;someType&gt; 变量(这些是大对象)。所有这些变量都有一个write 函数来向文件写入内容。我的想法是收集这些变量的所有指针,并在最后循环通过向量调用每个写入函数。现在,我可能想分配内存并删除A&lt;someType&gt; 变量。如果发生这种情况,它不应该执行写入函数。 因此,这听起来像是 shared_ptr 有意义的罕见情况之一,因为它允许您使用weak_ptr 观察对象的生命周期。我创建了一个更详细的示例来说明我的想法,并在我的答案中添加了解释,并且有一个Live Demo on Coliru

以上是关于检测 boos::variant 向量内的空指针的主要内容,如果未能解决你的问题,请参考以下文章

运行时错误:“ListNode”类型的空指针内的成员访问

不评估应用了 sizeof 的表达式是不是使得在 C++ 中取消引用 sizeof 内的空指针或无效指针是合法的?

OpenCV 和 ROS:cvMAT 中的空指针错误

用向量的向量的元素初始化向量的空向量

返回包含向量的空向量时的C++ Segfault

检查初始化列表中的空向量