创建库以覆盖迭代器的运算符*() - 冒险悬空指针 [英] Create library to override operator*() of iterator - risk dangling pointer
问题描述
我正在尝试创建自己的 boost :: adapters :: transformed
。
I am trying to create my own boost::adaptors::transformed
.
以下是相关的提升代码。
以下是它的用法(修改自 LogicStuff的答案): -
Here is its usage (modified from a SO answer by LogicStuff):-
C funcPointer(B& b){
//"funcPointer" is function convert from "B" to "C"
return instance-of-C
}
MyArray<B> test; //<-- any type, must already have begin() & end()
for(C c : test | boost::adaptor::transformed(funcPointer)) {
//... something ....
}
结果将与: -
for(auto b : test) {
C c = funcPointer(b);
//... something ...
}
我的尝试
我创建了 CollectAdapter
,其目的是像 boost :: adapter :: transformed
。
在大多数情况下都可以正常工作。
My Attempt
I created CollectAdapter
that aim to work like boost::adaptor::transformed
.
It works OK in most common cases.
Here is the full demo and back up. (same as below code)
有问题的部分是 CollectAdapter
- 我的库的核心。< br>
我不知道是否应该缓存集合_
按指针或按值。
The problematic part is CollectAdapter
- the core of my library.
I don't know whether I should cache the collection_
by-pointer or by-value.
CollectAdapter 封装基础集合_
(例如指向 std :: vector<>
): -
CollectAdapter encapsulates underlying collection_
(e.g. pointer to std::vector<>
) :-
template<class COLLECTION,class ADAPTER>class CollectAdapter{
using CollectAdapterT=CollectAdapter<COLLECTION,ADAPTER>;
COLLECTION* collection_; //<---- #1 problem? should cache by value?
ADAPTER adapter_; //<---- = func1 (or func2)
public: CollectAdapter(COLLECTION& collection,ADAPTER adapter){
collection_=&collection;
adapter_=adapter;
}
public: auto begin(){
return IteratorAdapter<
decltype(std::declval<COLLECTION>().begin()),
decltype(adapter_)>
(collection_->begin(),adapter_);
}
public: auto end(){ ..... }
};
IteratorAdapter
(在上面使用)封装底层迭代器,改变 operator *
的行为: -
template<class ITERATORT,class ADAPTER>class IteratorAdapter : public ITERATORT {
ADAPTER adapter_;
public: IteratorAdapter(ITERATORT underlying,ADAPTER adapter) :
ITERATORT(underlying),
adapter_(adapter)
{ }
public: auto operator*(){
return adapter_(ITERATORT::operator*());
}
};
CollectAdapterWidget
(在下面使用)只是一个帮助类来构建 CollectAdapter
-instance。
CollectAdapterWidget
(used below) is just a helper class to construct CollectAdapter
-instance.
它可以像: -
int func1(int i){ return i+10; }
int main(){
std::vector<int> test; test.push_back(5);
for(auto b:CollectAdapterWidget::createAdapter(test,func1)){
//^ create "CollectAdapter<std::vector<int>,func1>" instance
//here, b=5+10=15
}
}
问题
上述代码在大多数情况下都可以正常工作,除非 COLLECTION
是一个临时对象。
更具体地说,当我创建适配器适配器 适配器 时,可能会发生悬空指针。
More specifically, dangling pointer potentially occurs when I create adapter of adapter of adapter ....
int func1(int i){ return i+10; }
int func2(int i){ return i+100; }
template<class T> auto utilityAdapter(const T& t){
auto adapter1=CollectAdapterWidget::createAdapter(t,func1);
auto adapter12=CollectAdapterWidget::createAdapter(adapter1,func2);
//"adapter12.collection_" point to "adapter1"
return adapter12;
//end of scope, "adapter1" is deleted
//"adapter12.collection_" will be dangling pointer
}
int main(){
std::vector<int> test;
test.push_back(5);
for(auto b:utilityAdapter(test)){
std::cout<< b<<std::endl; //should 5+10+100 = 115
}
}
这个会导致运行时错误。这是 悬空指针演示 。
This will cause run time error. Here is the dangling-pointer demo.
在实际使用中,如果界面更棒,例如使用 |
运算符,将更难检测到错误: -
In the real usage, if the interface is more awesome, e.g. use |
operator, the bug will be even harder to be detected :-
//inside "utilityAdapter(t)"
return t|func1; //OK!
return t|func1|func2; //dangling pointer
问题
如何改进我的库以修复此错误,同时保持性能& 健壮性& 维持性接近同一级别?
Question
How to improve my library to fix this error while keeping performance & robustness & maintainablilty near the same level?
换句话说,如何缓存 COLLECTION $ c的数据或指针$ c>(可以适配器或真实数据结构)优雅?
In other words, how to cache data or pointer of COLLECTION
(that can be adapter or real data-structure) elegantly?
或者,如果是从头开始编码比修改我的代码更容易回答,去吧。 :)
Alternatively, if it is easier to answer by coding from scratch (than modifying my code), go for it. :)
当前代码通过指针缓存 。 >
解决方法的主要思想是缓存按值。
The current code caches by pointer.
The main idea of workarounds is to cache by value instead.
让适配器缓存 COLLECTION
的值。
以下是主要变化: -
Let adapter cache the value of COLLECTION
.
Here is the main change:-
COLLECTION collection_; //<------ #1
//changed from .... COLLECTION* collection_;
缺点: -
- 整个数据结构(例如
std :: vector
)将被复制值 - 浪费资源。
(直接用于std :: vector
)
- Whole data-structure (e.g.
std::vector
) will be value-copied - waste resource.
(when use forstd::vector
directly)
我将创建2个版本的库 - AdapterValue
和 AdapterPointer
。
我必须创建相关的类( Widget
, AdapterIterator
等。)。
I will create 2 versions of the library - AdapterValue
and AdapterPointer
.
I have to create related classes (Widget
,AdapterIterator
,etc.) as well.
-
AdapterValue
- 按值。 (专为utilityAdapter()
而设计) -
AdapterPointer
- by指针即可。 (专为std :: vector
而设计)
AdapterValue
- by value. (designed forutilityAdapter()
)AdapterPointer
- by pointer. (designed forstd::vector
)
缺点: -
- 重复代码=低可维护性
- 用户(编码员)必须非常清楚选择哪一个=低稳健性
我可以使用模板专精来执行此操作: -
I may use template specialization that do this :-
If( COLLECTION is an "CollectAdapter" ){ by value }
Else{ by pointer }
缺点: -
- 许多适配器类之间不合作。
他们必须互相认识:认可 =应按值隐藏 。
- Not cooperate well between many adapter classes.
They have to recognize each other : recognized = should cache by value.
很抱歉很长的帖子。
推荐答案
我个人会选择模板专业化–但是,不是专门化原始模板,而是专门设置嵌套类:
I personally would go with template specialisation – however, not specialise the original template, but a nested class instead:
template<typename Collection, typename Adapter>
class CollectAdapter
{
template<typename C>
class ObjectKeeper // find some better name yourself...
{
C* object;
public:
C* operator*() { return object; };
C* operator->() { return object; };
};
template<typename C, typename A>
class ObjectKeeper <CollectAdapter<C, A>>
{
CollectAdapter<C, A> object;
public:
CollectAdapter<C, A>* operator*() { return &object; };
CollectAdapter<C, A>* operator->() { return &object; };
};
ObjectKeeper<Collection> keeper;
// now use *keeper or keeper-> wherever needed
};
外部类通过只使用指针来覆盖这两种情况,而嵌套类隐藏了差异。
The outer class then covers both cases by just always using pointers while the nested class hides the differences away.
当然,不完整(你需要添加适当的构造函数,例如,外部和内部类),但它应该给你的想法...
Sure, incomplete (you need yet need to add appropriate constructors, for instance, both to outer and inner class), but it should give you the idea...
您甚至可以允许用户选择他/她想要复制:
You might even allow the user to select if he/she wants to copy:
template<typename Collection, typename Adapter, bool IsAlwaysCopy = false>
class CollectAdapter
{
template<typename C, bool IsCopy>
class ObjectWrapper // find some better name yourself...
{
C* object;
public:
C* operator*() { return object; };
C* operator->() { return object; };
};
template<typename C>
class ObjectWrapper<C, true>
{
C object;
public:
C* operator*() { return &object; };
C* operator->() { return &object; };
};
// avoiding code duplication...
template<typename C, bool IsCopy>
class ObjectKeeper : public ObjectWrapper<C, IsCopy>
{ };
template<typename C, typename A, bool IsCopy>
class ObjectKeeper <CollectAdapter<C, A>, IsCopy>
: public ObjectWrapper<CollectAdapter<C, A>, true>
{ };
ObjectKeeper<Collection> keeper;
};
这篇关于创建库以覆盖迭代器的运算符*() - 冒险悬空指针的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!