1 //
2 // XPtr.h: Rcpp R/C++ interface class library -- smart external pointers
3 //
4 // Copyright (C) 2009 - 2020  Dirk Eddelbuettel and Romain Francois
5 //
6 // This file is part of Rcpp.
7 //
8 // Rcpp is free software: you can redistribute it and/or modify it
9 // under the terms of the GNU General Public License as published by
10 // the Free Software Foundation, either version 2 of the License, or
11 // (at your option) any later version.
12 //
13 // Rcpp is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with Rcpp.  If not, see <http://www.gnu.org/licenses/>.
20 
21 #ifndef Rcpp_XPtr_h
22 #define Rcpp_XPtr_h
23 
24 #include <RcppCommon.h>
25 
26 namespace Rcpp {
27 
28 template <typename T>
standard_delete_finalizer(T * obj)29 void standard_delete_finalizer(T* obj) {												// #nocov start
30     delete obj;
31 }
32 
33 template <typename T, void Finalizer(T*) >
finalizer_wrapper(SEXP p)34 void finalizer_wrapper(SEXP p) {
35     if (TYPEOF(p) != EXTPTRSXP)
36         return;
37 
38     T* ptr = (T*) R_ExternalPtrAddr(p);
39     RCPP_DEBUG_3("finalizer_wrapper<%s>(SEXP p = <%p>). ptr = %p", DEMANGLE(T), p, ptr)
40 
41     if (ptr == NULL)
42         return;
43 
44     // Clear before finalizing to avoid behavior like access of freed memory
45     R_ClearExternalPtr(p);
46 
47     Finalizer(ptr);
48 }																						// #nocov end
49 
50 template <
51     typename T,
52     template <class> class StoragePolicy = PreserveStorage,
53     void Finalizer(T*) = standard_delete_finalizer<T>,
54     bool finalizeOnExit = false
55 >
56 class XPtr :
57     public StoragePolicy< XPtr<T,StoragePolicy, Finalizer, finalizeOnExit> >,
58     public SlotProxyPolicy< XPtr<T,StoragePolicy, Finalizer, finalizeOnExit> >,
59     public AttributeProxyPolicy< XPtr<T,StoragePolicy, Finalizer, finalizeOnExit> >,
60     public TagProxyPolicy< XPtr<T,StoragePolicy, Finalizer, finalizeOnExit> >,
61     public ProtectedProxyPolicy< XPtr<T,StoragePolicy, Finalizer, finalizeOnExit> >,
62     public RObjectMethods< XPtr<T,StoragePolicy, Finalizer, finalizeOnExit> >
63 {
64 public:
65 
66     typedef StoragePolicy<XPtr> Storage;
67 
68     /**
69      * constructs a XPtr wrapping the external pointer (EXTPTRSXP SEXP)
70      *
71      * @param xp external pointer to wrap
72      */
XPtr(SEXP x)73     explicit XPtr(SEXP x) { checked_set(x); };
74 
75     /**
76      * constructs a XPtr wrapping the external pointer (EXTPTRSXP SEXP)
77      *
78      * @param xp external pointer to wrap
79      * @param tag tag to assign to external pointer
80      * @param prot protected data to assign to external pointer
81      */
XPtr(SEXP x,SEXP tag,SEXP prot)82     explicit XPtr(SEXP x, SEXP tag, SEXP prot) {
83         checked_set(x);
84         R_SetExternalPtrTag(x, tag);
85         R_SetExternalPtrProtected(x, prot);
86     };
87 
88     /**
89      * creates a new external pointer wrapping the dumb pointer p.
90      *
91      * @param p dumb pointer to some object
92      * @param set_delete_finalizer if set to true, a finalizer will
93      *        be registered for the external pointer. The finalizer
94      *        is called when the xp is garbage collected. The finalizer
95      *        is merely a call to the delete operator or the pointer
96      *        so you need to make sure the pointer can be "delete" d
97      *        this way (has to be a C++ object)
98      */
99     explicit XPtr(T* p, bool set_delete_finalizer = true,
100                   SEXP tag = R_NilValue, SEXP prot = R_NilValue) {
101         RCPP_DEBUG_2("XPtr(T* p = <%p>, bool set_delete_finalizer = %s, SEXP tag = R_NilValue, SEXP prot = R_NilValue)", p, (set_delete_finalizer ? "true" : "false"))
102         Storage::set__(R_MakeExternalPtr((void*)p , tag, prot));
103         if (set_delete_finalizer) {
104             setDeleteFinalizer();												// #nocov
105         }
106     }
107 
XPtr(const XPtr & other)108     XPtr(const XPtr& other) {
109         Storage::copy__(other);
110     }
111 
112     XPtr& operator=(const XPtr& other) {
113         Storage::copy__(other);
114         return *this;
115     }
116 
117     /**
118      * Typesafe accessor for underlying pointer (use checked_get
119      * if you want an exception thrown if the pointer is NULL)
120      */
get()121     inline T* get() const {
122         return (T*)(R_ExternalPtrAddr(Storage::get__()));
123     }
124 
125     /**
126      * Boolean operator wrapper for get() using the "safe bool idiom", see:
127      * http://www.boost.org/doc/libs/1_57_0/boost/smart_ptr/detail/operator_bool.hpp
128      */
129     typedef void (*unspecified_bool_type)();
unspecified_bool_true()130     static void unspecified_bool_true() {}
unspecified_bool_type()131     operator unspecified_bool_type() const {
132         return get() == NULL ? 0 : unspecified_bool_true;
133     }
134     bool operator!() const {
135         return get() == NULL;
136     }
137 
138     /**
139      * Access underlying pointer throwing an exception if the ptr is NULL
140      */
checked_get()141     inline T* checked_get() const {
142         T* ptr = get();
143         if (ptr == NULL)
144             throw ::Rcpp::exception("external pointer is not valid");									// #nocov
145         return ptr;
146     }
147 
148     /**
149      * Returns a reference to the object wrapped. This allows this
150      * object to look and feel like a dumb pointer to T
151      */
152     T& operator*() const {
153         return *(checked_get());
154     }
155 
156     /**
157      * Returns the dumb pointer. This allows to call the -> operator
158      * on this as if it was the dumb pointer
159      */
160     T* operator->() const {
161         return checked_get();
162     }
163 
setDeleteFinalizer()164     void setDeleteFinalizer() {																			// #nocov start
165         R_RegisterCFinalizerEx(Storage::get__(), finalizer_wrapper<T,Finalizer>,
166                                (Rboolean) finalizeOnExit);
167     }																									// #nocov end
168 
169     /**
170      * Release the external pointer (if any) immediately. This will cause
171      * the pointer to be deleted and it's storage to be set to NULL.
172      * After this call the get() method returns NULL and the checked_get()
173      * method throws an exception.
174      *
175      * See the discussion here for the basic logic behind release:
176      * https://stat.ethz.ch/pipermail/r-help/2007-August/137871.html
177      */
release()178     void release() {
179 
180         if (get() != NULL) {
181             // Call the finalizer -- note that this implies that finalizers
182             // need to be ready for a NULL external pointer value (our
183             // default C++ finalizer is since delete NULL is a no-op).
184             // This clears the external pointer just before calling the finalizer,
185             // to avoid interesting behavior with co-dependent finalizers.
186             finalizer_wrapper<T,Finalizer>(Storage::get__());
187         }
188     }
189 
190     inline operator T*() {
191         return checked_get();
192     }
193 
update(SEXP)194     void update(SEXP) {}
195 
196 private:
checked_set(SEXP x)197     inline void checked_set(SEXP x) {
198         if (TYPEOF(x) != EXTPTRSXP) {
199             const char* fmt = "Expecting an external pointer: [type=%s].";						// #nocov
200             throw ::Rcpp::not_compatible(fmt, Rf_type2char(TYPEOF(x)));							// #nocov
201         }
202         Storage::set__(x);
203     }
204 
205 };
206 
207 } // namespace Rcpp
208 
209 #endif
210