Skip to content

Commit 7752f2c

Browse files
committed
[cpp2py] Add option wrapped_members_as_shared_refs
Previously member of a wrapped type were copied on access This commit introduces an option where access of wrapped members will avoid the copy at the cost of keeping the parent object alive for the full lifetime of the newly generated reference
1 parent 7fa3595 commit 7752f2c

File tree

6 files changed

+54
-21
lines changed

6 files changed

+54
-21
lines changed

bin/c++2py.in

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ parser.add_argument('--includes', '-I', action='append', help='Includes to pass
3737
parser.add_argument('--system_includes', '-isystem', action='append', help='System includes to pass to clang')
3838
parser.add_argument('--cxxflags', default = '', help='Options to pass to clang')
3939
parser.add_argument('--target_file_only', action='store_true', help='Disable recursion into included header files')
40+
parser.add_argument('--wrapped_members_as_shared_refs', action='store_true', help='Disable recursion into included header files')
4041

4142
args = parser.parse_args()
4243

@@ -77,7 +78,8 @@ W= Cpp2Desc(filename = args.filename,
7778
shell_command = shell_command,
7879
parse_all_comments = args.parse_all_comments,
7980
namespace_to_factor= (), # unused now
80-
target_file_only = args.target_file_only
81+
target_file_only = args.target_file_only,
82+
wrapped_members_as_shared_refs = args.wrapped_members_as_shared_refs
8183
)
8284

8385
# Make the desc file

cpp2py/cpp2desc.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Cpp2Desc:
88
""" """
99
def __init__(self, filename, namespaces=(), classes= (), namespace_to_factor= (), appname= '',
1010
modulename = '', moduledoc ='', use_properties = False, members_read_only = True, converters = (),
11-
compiler_options=None, includes= None, system_includes= None, libclang_location = None, shell_command = '', parse_all_comments = True, target_file_only = False):
11+
compiler_options=None, includes= None, system_includes= None, libclang_location = None, shell_command = '', parse_all_comments = True, target_file_only = False, wrapped_members_as_shared_refs = False):
1212
"""
1313
Parse the file at construction
1414
@@ -59,9 +59,12 @@ def __init__(self, filename, namespaces=(), classes= (), namespace_to_factor= ()
5959
6060
target_file_only : bool
6161
Neglect any included files during desc generation [default = False]
62+
63+
wrapped_members_as_shared_refs : bool
64+
For classes with members which are a wrapped type, do not copy them on access but return them as shared references instead. Note that members with types that are only converted (e.g. std::vector) will continue to be copied on access [default = False]
6265
"""
63-
for x in ['filename', 'namespaces', 'classes', 'namespace_to_factor', 'appname', 'modulename', 'moduledoc',
64-
'use_properties', 'members_read_only', 'shell_command', 'target_file_only']:
66+
for x in ['filename', 'namespaces', 'classes', 'namespace_to_factor', 'appname', 'modulename', 'moduledoc',
67+
'use_properties', 'members_read_only', 'shell_command', 'target_file_only', 'wrapped_members_as_shared_refs']:
6568
setattr(self, x, locals()[x])
6669
self.DE = dependency_analyzer.DependencyAnalyzer(converters)
6770
# parse the file

cpp2py/mako/desc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from cpp2py.wrap_generator import *
44

55
# The module
6-
module = module_(full_name = "${W.modulename}", doc = r"${doc.replace_latex(W.moduledoc)}", app_name = "${W.appname}")
6+
module = module_(full_name = "${W.modulename}", doc = r"${doc.replace_latex(W.moduledoc)}", app_name = "${W.appname}", wrapped_members_as_shared_refs = ${W.wrapped_members_as_shared_refs})
77

88
# Imports
99
%if import_list:

cpp2py/mako/wrap.cxx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#include<iostream> //for std::cout...
55
using dcomplex = std::complex<double>;
66

7+
// global options
8+
constexpr bool wrapped_members_as_shared_refs = ${int(module.wrapped_members_as_shared_refs)};
9+
710
// first the basic stuff
811
#include <cpp2py.hpp>
912
#include <cpp2py/converters/string.hpp>
@@ -230,6 +233,7 @@ static PyObject* ${c.py_type}_richcompare (PyObject *a, PyObject *b, int op);
230233
typedef struct {
231234
PyObject_HEAD
232235
${c.c_type} * _c;
236+
PyObject * parent = nullptr;
233237
} ${c.py_type};
234238

235239
## The new function, only if there is constructor
@@ -245,8 +249,9 @@ static PyObject* ${c.py_type}_new(PyTypeObject *type, PyObject *args, PyObject *
245249

246250
// dealloc
247251
static void ${c.py_type}_dealloc(${c.py_type}* self) {
248-
if (self->_c != NULL) delete self->_c; // should never be null, but I protect it anyway
249-
self->ob_type->tp_free((PyObject*)self);
252+
if ((self->_c != NULL) and (self->parent == nullptr)) delete self->_c; // should never be null, but I protect it anyway
253+
Py_XDECREF(self->parent);
254+
Py_TYPE(self)->tp_free((PyObject*)self);
250255
}
251256

252257
//--------------------- Iterator by wrapping the C++ -----------------------------
@@ -601,7 +606,7 @@ template <> struct py_converter<${en.c_name}> {
601606

602607
static PyObject * ${c.py_type}__get_member_${m.py_name} (PyObject *self, void *closure) {
603608
auto & self_c = convert_from_python<${c.c_type}>(self);
604-
return convert_to_python(self_c.${m.c_name});
609+
return convert_to_python(self_c.${m.c_name}, self);
605610
}
606611

607612
%if not m.read_only:

cpp2py/wrap_generator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ class module_ :
680680
"""
681681
Representation of a module
682682
"""
683-
def __init__(self, full_name, doc = '', app_name = None) :
683+
def __init__(self, full_name, doc = '', app_name = None, wrapped_members_as_shared_refs = False) :
684684
"""
685685
Parameters
686686
----------
@@ -693,6 +693,7 @@ def __init__(self, full_name, doc = '', app_name = None) :
693693
694694
"""
695695
self.full_name = full_name if app_name is None or app_name=="triqs" else app_name+"."+full_name
696+
self.wrapped_members_as_shared_refs = wrapped_members_as_shared_refs
696697
self.name = full_name.rsplit('.',1)[-1]
697698
self.doc = doc
698699
self.classes = {} # dict : string -> class_. Key is the Python type

include/cpp2py/py_converter.hpp

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,20 +97,36 @@ namespace cpp2py {
9797

9898
// default version is that the type is wrapped.
9999
// Will be specialized for type which are just converted.
100-
template <typename T> struct py_converter {
100+
template <typename TUREF> struct py_converter {
101+
102+
using T = std::decay_t<TUREF>;
103+
static constexpr bool is_ref = std::is_reference_v<TUREF>;
101104

102105
typedef struct {
103106
PyObject_HEAD;
104107
T *_c;
108+
PyObject *parent = nullptr;
105109
} py_type;
106110

107111
using is_wrapped_type = void; // to recognize
108112

109-
template <typename U> static PyObject *c2py(U &&x) {
113+
template <typename U> static PyObject *c2py(U &&x, PyObject *parent = nullptr) {
110114
PyTypeObject *p = get_type_ptr(typeid(T));
111115
if (p == nullptr) return NULL;
112116
py_type *self = (py_type *)p->tp_alloc(p, 0);
113-
if (self != NULL) { self->_c = new T{std::forward<U>(x)}; }
117+
if (self != NULL) {
118+
if constexpr (is_ref && wrapped_members_as_shared_refs) {
119+
// Keep parent alive for lifetime of self
120+
if (parent != nullptr) {
121+
self->parent = parent;
122+
Py_INCREF(parent);
123+
self->_c = &x;
124+
return (PyObject *)self;
125+
}
126+
}
127+
// Create heap copy of x to guarantee lifetime
128+
self->_c = new T{std::forward<U>(x)};
129+
}
114130
return (PyObject *)self;
115131
}
116132

@@ -128,7 +144,7 @@ namespace cpp2py {
128144
if (p == nullptr) return false;
129145
if (PyObject_TypeCheck(ob, p)) {
130146
if (((py_type *)ob)->_c != NULL) return true;
131-
auto err = std::string{"Severe internal error : Python object of "} + p->tp_name + " has a _c NULL pointer !!";
147+
auto err = std::string{"Severe internal error : Python object of "} + p->tp_name + " has a _c NULL pointer !!";
132148
if (raise_exception) PyErr_SetString(PyExc_TypeError, err.c_str());
133149
return false;
134150
}
@@ -138,6 +154,12 @@ namespace cpp2py {
138154
}
139155
};
140156

157+
// is_wrapped<T> if py_converter has been reimplemented.
158+
template <typename T, class = void> struct is_wrapped : std::false_type {};
159+
template <typename T> struct is_wrapped<T, typename py_converter<T>::is_wrapped_type> : std::true_type {};
160+
161+
template <typename T> constexpr bool is_wrapped_v = is_wrapped<T>::value;
162+
141163
// helpers for better error message
142164
// some class (e.g. range !) only have ONE conversion, i.e. C -> Py, but not both
143165
// we need to distinguish
@@ -149,9 +171,15 @@ namespace cpp2py {
149171
struct does_have_a_converterC2Py<T, __std17::void_t<decltype(py_converter<std::decay_t<T>>::c2py(std::declval<T>()))>> : std::true_type {};
150172

151173
// We only use these functions in the code, not directly the converter
152-
template <typename T> static PyObject *convert_to_python(T &&x) {
174+
template <typename T> static PyObject *convert_to_python(T &&x, PyObject *parent = nullptr) {
153175
static_assert(does_have_a_converterC2Py<T>::value, "The type does not have a converter from C++ to Python");
154-
return py_converter<std::decay_t<T>>::c2py(std::forward<T>(x));
176+
PyObject *r;
177+
if constexpr (is_wrapped_v<std::decay_t<T>>) {
178+
r = py_converter<T>::c2py(std::forward<T>(x), parent);
179+
} else { // Converted type
180+
r = py_converter<std::decay_t<T>>::c2py(std::forward<T>(x));
181+
}
182+
return r;
155183
}
156184
template <typename T> static bool convertible_from_python(PyObject *ob, bool raise_exception) {
157185
return py_converter<T>::is_convertible(ob, raise_exception);
@@ -169,12 +197,6 @@ namespace cpp2py {
169197
*
170198
*/
171199

172-
// is_wrapped<T> if py_converter has been reimplemented.
173-
template <typename T, class = void> struct is_wrapped : std::false_type {};
174-
template <typename T> struct is_wrapped<T, typename py_converter<T>::is_wrapped_type> : std::true_type {};
175-
176-
template <typename T> constexpr bool is_wrapped_v = is_wrapped<T>::value;
177-
178200
template <typename T> static auto convert_from_python(PyObject *ob) -> decltype(py_converter<T>::py2c(ob)) {
179201
static_assert(does_have_a_converterPy2C<T>::value, "The type does not have a converter from Python to C++");
180202
return py_converter<T>::py2c(ob);

0 commit comments

Comments
 (0)