Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create library to override operator*() of iterator - risk dangling pointer

Tags:

I am trying to create my own boost::adaptors::transformed.

Here is the related boost code.

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 .... } 

The result will be the same as :-

for(auto b : test) {     C c = funcPointer(b);     //... something ... } 

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)

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 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 (used above) encapsulates underlying iterator, change behavior of 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 (used below) is just a helper class to construct CollectAdapter-instance.

It can be used like:-

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     } }   

Problem

The above code works OK in most cases, except when COLLECTION is a temporary object.

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?

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. :)

My workarounds

The current code caches by pointer.
The main idea of workarounds is to cache by value instead.

Workaround 1 (always "by value")

Let adapter cache the value of COLLECTION.
Here is the main change:-

COLLECTION collection_;    //<------ #1  //changed from   .... COLLECTION* collection_; 

Disadvantage:-

  • Whole data-structure (e.g. std::vector) will be value-copied - waste resource.
    (when use for std::vector directly)

Workaround 2 (two versions of library, best?)

I will create 2 versions of the library - AdapterValue and AdapterPointer.
I have to create related classes (Widget,AdapterIterator,etc.) as well.

  • AdapterValue - by value. (designed for utilityAdapter())
  • AdapterPointer - by pointer. (designed for std::vector)

Disadvantage:-

  • Duplicate code a lot = low maintainability
  • Users (coders) have to be very conscious about which one to pick = low robustness

Workaround 3 (detect type)

I may use template specialization that do this :-

If( COLLECTION is an "CollectAdapter" ){ by value }   Else{ by pointer }     

Disadvantage:-

  • Not cooperate well between many adapter classes.
    They have to recognize each other : recognized = should cache by value.

Sorry for very long post.

like image 973
javaLover Avatar asked Feb 21 '17 07:02

javaLover


People also ask

How to overload structure pointer operator->?

The structure pointer operator -> can be overloaded as a nonstatic class member function. The overloaded structure pointer operator is a unary operator on its left operand. The argument must be either a class object or a reference of this type.

Can a user-defined type overload a operator?

A user-defined type can overload a predefined C# operator. That is, a type can provide the custom implementation of an operation in case one or both of the operands are of that type. The Overloadable operators section shows which C# operators can be overloaded.

What is library overrides?

Library Overrides is a system designed to replace and supersede Proxies. Most types of linked data-blocks can be overridden, and the properties of those overrides can then be edited. When the library data changes, unmodified properties of the overridden one will be updated accordingly.

What happens if two iterators point to different elements in container?

If two iterators point to different elements in a container, then they are not equal. The first two template operators return true only if both left and right store the same iterator. The third template operator returns true only if both left and right store the same stream pointer.


1 Answers

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 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 she/he 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; }; 
like image 171
Aconcagua Avatar answered Oct 17 '22 04:10

Aconcagua