检测 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?
【问题讨论】:
delete
ing nullptr
不是错误(它是无操作),但未使用 new
分配的 delete
ing 指针是。更重要的是,它是未定义的行为,你不会 catch
任何异常。
你的意思是tst == nullptr
还是...?
在“现代”C++ 中,通常不需要在多态性之外使用指针。如果你需要指针,不要使用普通的非拥有指针,而是使用像std::unique_ptr
这样的智能指针。
你能用boost::variant2::variant
吗?它有boost::monostate
。将其设为variant
中的第一个类型,以便之后检查它是否包含任何有用的内容 - 并且不要存储指针。请改用variant<boost::monostate, A<int>, A<double> >
。
nullptr
与variant
的关系如何?您有一个变体向量,并使用 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<<
使用经典的动态多态,如果不添加虚拟方法,这将无法工作,但使用 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<someType>
变量(这些是大对象)。所有这些变量都有一个写入函数,可以将内容写入文件。我的想法是收集这些变量的所有指针,并在最后循环通过向量来调用每个写入函数。现在,我可能想分配内存并删除
A<someType>
变量。如果发生这种情况,它不应该执行 write 函数。
这听起来像是shared_ptr
有意义的罕见情况之一,因为它允许您使用weak_ptr
观察对象的生命周期。
想象的对象图...
让我们发明一种节点类型,它可以参与一个非常大的对象图,这样您就可以保留指向它的某些节点的指针的“索引”。对于这个演示,我将把它做成一个树状结构图,我们将把Reference
s 保留在叶节点上:
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);
这会将其所有叶节点Reference
s 放入一个容器中:
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<>
99% 是无用的,而且可能不是您想要的。你能描述一下你真正想要解决的问题,而不是你认为它应该是什么样子吗?
是的,当然。我想创建 n 个A<someType>
变量(这些是大对象)。所有这些变量都有一个write
函数来向文件写入内容。我的想法是收集这些变量的所有指针,并在最后循环通过向量调用每个写入函数。现在,我可能想分配内存并删除A<someType>
变量。如果发生这种情况,它不应该执行写入函数。
因此,这听起来像是 shared_ptr 有意义的罕见情况之一,因为它允许您使用weak_ptr 观察对象的生命周期。我创建了一个更详细的示例来说明我的想法,并在我的答案中添加了解释,并且有一个Live Demo on Coliru以上是关于检测 boos::variant 向量内的空指针的主要内容,如果未能解决你的问题,请参考以下文章