Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
gaoqiong
pybind11
Commits
af2dda38
Commit
af2dda38
authored
Jun 26, 2017
by
Dean Moldovan
Browse files
Add a life support system for type_caster temporaries
parent
6b442ff9
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
97 additions
and
7 deletions
+97
-7
include/pybind11/cast.h
include/pybind11/cast.h
+52
-7
include/pybind11/common.h
include/pybind11/common.h
+1
-0
include/pybind11/pybind11.h
include/pybind11/pybind11.h
+2
-0
tests/test_class.cpp
tests/test_class.cpp
+34
-0
tests/test_class.py
tests/test_class.py
+8
-0
No files found.
include/pybind11/cast.h
View file @
af2dda38
...
@@ -111,6 +111,53 @@ PYBIND11_NOINLINE inline internals &get_internals() {
...
@@ -111,6 +111,53 @@ PYBIND11_NOINLINE inline internals &get_internals() {
return
*
internals_ptr
;
return
*
internals_ptr
;
}
}
/// A life support system for temporary objects created by `type_caster::load()`.
/// Adding a patient will keep it alive up until the enclosing function returns.
class
loader_life_support
{
public:
/// A new patient frame is created when a function is entered
loader_life_support
()
{
get_internals
().
loader_patient_stack
.
push_back
(
nullptr
);
}
/// ... and destroyed after it returns
~
loader_life_support
()
{
auto
&
stack
=
get_internals
().
loader_patient_stack
;
if
(
stack
.
empty
())
pybind11_fail
(
"loader_life_support: internal error"
);
auto
ptr
=
stack
.
back
();
stack
.
pop_back
();
Py_CLEAR
(
ptr
);
// A heuristic to reduce the stack's capacity (e.g. after long recursive calls)
if
(
stack
.
capacity
()
>
16
&&
stack
.
size
()
!=
0
&&
stack
.
capacity
()
/
stack
.
size
()
>
2
)
stack
.
shrink_to_fit
();
}
/// This can only be used inside a pybind11-bound function, either by `argument_loader`
/// at argument preparation time or by `py::cast()` at execution time.
PYBIND11_NOINLINE
static
void
add_patient
(
handle
h
)
{
auto
&
stack
=
get_internals
().
loader_patient_stack
;
if
(
stack
.
empty
())
throw
cast_error
(
"When called outside a bound function, py::cast() cannot "
"do Python -> C++ conversions which require the creation "
"of temporary values"
);
auto
&
list_ptr
=
stack
.
back
();
if
(
list_ptr
==
nullptr
)
{
list_ptr
=
PyList_New
(
1
);
if
(
!
list_ptr
)
pybind11_fail
(
"loader_life_support: error allocating list"
);
PyList_SET_ITEM
(
list_ptr
,
0
,
h
.
inc_ref
().
ptr
());
}
else
{
auto
result
=
PyList_Append
(
list_ptr
,
h
.
ptr
());
if
(
result
==
-
1
)
pybind11_fail
(
"loader_life_support: error adding patient"
);
}
}
};
// Gets the cache entry for the given type, creating it if necessary. The return value is the pair
// Gets the cache entry for the given type, creating it if necessary. The return value is the pair
// returned by emplace, i.e. an iterator for the entry and a bool set to `true` if the entry was
// returned by emplace, i.e. an iterator for the entry and a bool set to `true` if the entry was
// just created.
// just created.
...
@@ -643,9 +690,11 @@ protected:
...
@@ -643,9 +690,11 @@ protected:
// Perform an implicit conversion
// Perform an implicit conversion
if
(
convert
)
{
if
(
convert
)
{
for
(
auto
&
converter
:
typeinfo
->
implicit_conversions
)
{
for
(
auto
&
converter
:
typeinfo
->
implicit_conversions
)
{
temp
=
reinterpret_steal
<
object
>
(
converter
(
src
.
ptr
(),
typeinfo
->
type
));
auto
temp
=
reinterpret_steal
<
object
>
(
converter
(
src
.
ptr
(),
typeinfo
->
type
));
if
(
load_impl
<
ThisT
>
(
temp
,
false
))
if
(
load_impl
<
ThisT
>
(
temp
,
false
))
{
loader_life_support
::
add_patient
(
temp
);
return
true
;
return
true
;
}
}
}
if
(
this_
.
try_direct_conversions
(
src
))
if
(
this_
.
try_direct_conversions
(
src
))
return
true
;
return
true
;
...
@@ -674,7 +723,6 @@ protected:
...
@@ -674,7 +723,6 @@ protected:
const
type_info
*
typeinfo
=
nullptr
;
const
type_info
*
typeinfo
=
nullptr
;
void
*
value
=
nullptr
;
void
*
value
=
nullptr
;
object
temp
;
};
};
/**
/**
...
@@ -1064,7 +1112,7 @@ template <typename StringType, bool IsView = false> struct string_caster {
...
@@ -1064,7 +1112,7 @@ template <typename StringType, bool IsView = false> struct string_caster {
// If we're loading a string_view we need to keep the encoded Python object alive:
// If we're loading a string_view we need to keep the encoded Python object alive:
if
(
IsView
)
if
(
IsView
)
view_into
=
std
::
move
(
utfNbytes
);
loader_life_support
::
add_patient
(
utfNbytes
);
return
true
;
return
true
;
}
}
...
@@ -1080,8 +1128,6 @@ template <typename StringType, bool IsView = false> struct string_caster {
...
@@ -1080,8 +1128,6 @@ template <typename StringType, bool IsView = false> struct string_caster {
PYBIND11_TYPE_CASTER
(
StringType
,
_
(
PYBIND11_STRING_NAME
));
PYBIND11_TYPE_CASTER
(
StringType
,
_
(
PYBIND11_STRING_NAME
));
private:
private:
object
view_into
;
static
handle
decode_utfN
(
const
char
*
buffer
,
ssize_t
nbytes
)
{
static
handle
decode_utfN
(
const
char
*
buffer
,
ssize_t
nbytes
)
{
#if !defined(PYPY_VERSION)
#if !defined(PYPY_VERSION)
return
return
...
@@ -1336,7 +1382,6 @@ public:
...
@@ -1336,7 +1382,6 @@ public:
using
base
::
cast
;
using
base
::
cast
;
using
base
::
typeinfo
;
using
base
::
typeinfo
;
using
base
::
value
;
using
base
::
value
;
using
base
::
temp
;
bool
load
(
handle
src
,
bool
convert
)
{
bool
load
(
handle
src
,
bool
convert
)
{
return
base
::
template
load_impl
<
copyable_holder_caster
<
type
,
holder_type
>
>
(
src
,
convert
);
return
base
::
template
load_impl
<
copyable_holder_caster
<
type
,
holder_type
>
>
(
src
,
convert
);
...
...
include/pybind11/common.h
View file @
af2dda38
...
@@ -474,6 +474,7 @@ struct internals {
...
@@ -474,6 +474,7 @@ struct internals {
std
::
unordered_map
<
const
PyObject
*
,
std
::
vector
<
PyObject
*>>
patients
;
std
::
unordered_map
<
const
PyObject
*
,
std
::
vector
<
PyObject
*>>
patients
;
std
::
forward_list
<
void
(
*
)
(
std
::
exception_ptr
)
>
registered_exception_translators
;
std
::
forward_list
<
void
(
*
)
(
std
::
exception_ptr
)
>
registered_exception_translators
;
std
::
unordered_map
<
std
::
string
,
void
*>
shared_data
;
// Custom data to be shared across extensions
std
::
unordered_map
<
std
::
string
,
void
*>
shared_data
;
// Custom data to be shared across extensions
std
::
vector
<
PyObject
*>
loader_patient_stack
;
// Used by `loader_life_support`
PyTypeObject
*
static_property_type
;
PyTypeObject
*
static_property_type
;
PyTypeObject
*
default_metaclass
;
PyTypeObject
*
default_metaclass
;
PyObject
*
instance_base
;
PyObject
*
instance_base
;
...
...
include/pybind11/pybind11.h
View file @
af2dda38
...
@@ -573,6 +573,7 @@ protected:
...
@@ -573,6 +573,7 @@ protected:
// 6. Call the function.
// 6. Call the function.
try
{
try
{
loader_life_support
guard
{};
result
=
func
.
impl
(
call
);
result
=
func
.
impl
(
call
);
}
catch
(
reference_cast_error
&
)
{
}
catch
(
reference_cast_error
&
)
{
result
=
PYBIND11_TRY_NEXT_OVERLOAD
;
result
=
PYBIND11_TRY_NEXT_OVERLOAD
;
...
@@ -601,6 +602,7 @@ protected:
...
@@ -601,6 +602,7 @@ protected:
// The no-conversion pass finished without success, try again with conversion allowed
// The no-conversion pass finished without success, try again with conversion allowed
for
(
auto
&
call
:
second_pass
)
{
for
(
auto
&
call
:
second_pass
)
{
try
{
try
{
loader_life_support
guard
{};
result
=
call
.
func
.
impl
(
call
);
result
=
call
.
func
.
impl
(
call
);
}
catch
(
reference_cast_error
&
)
{
}
catch
(
reference_cast_error
&
)
{
result
=
PYBIND11_TRY_NEXT_OVERLOAD
;
result
=
PYBIND11_TRY_NEXT_OVERLOAD
;
...
...
tests/test_class.cpp
View file @
af2dda38
...
@@ -150,6 +150,40 @@ TEST_SUBMODULE(class_, m) {
...
@@ -150,6 +150,40 @@ TEST_SUBMODULE(class_, m) {
py
::
class_
<
MyDerived
,
MyBase
>
(
m
,
"MyDerived"
)
py
::
class_
<
MyDerived
,
MyBase
>
(
m
,
"MyDerived"
)
.
def_static
(
"make"
,
&
MyDerived
::
make
)
.
def_static
(
"make"
,
&
MyDerived
::
make
)
.
def_static
(
"make2"
,
&
MyDerived
::
make
);
.
def_static
(
"make2"
,
&
MyDerived
::
make
);
// test_implicit_conversion_life_support
struct
ConvertibleFromUserType
{
int
i
;
ConvertibleFromUserType
(
UserType
u
)
:
i
(
u
.
value
())
{
}
};
py
::
class_
<
ConvertibleFromUserType
>
(
m
,
"AcceptsUserType"
)
.
def
(
py
::
init
<
UserType
>
());
py
::
implicitly_convertible
<
UserType
,
ConvertibleFromUserType
>
();
m
.
def
(
"implicitly_convert_argument"
,
[](
const
ConvertibleFromUserType
&
r
)
{
return
r
.
i
;
});
m
.
def
(
"implicitly_convert_variable"
,
[](
py
::
object
o
)
{
// `o` is `UserType` and `r` is a reference to a temporary created by implicit
// conversion. This is valid when called inside a bound function because the temp
// object is attached to the same life support system as the arguments.
const
auto
&
r
=
o
.
cast
<
const
ConvertibleFromUserType
&>
();
return
r
.
i
;
});
m
.
add_object
(
"implicitly_convert_variable_fail"
,
[
&
]
{
auto
f
=
[](
PyObject
*
,
PyObject
*
args
)
->
PyObject
*
{
auto
o
=
py
::
reinterpret_borrow
<
py
::
tuple
>
(
args
)[
0
];
try
{
// It should fail here because there is no life support.
o
.
cast
<
const
ConvertibleFromUserType
&>
();
}
catch
(
const
py
::
cast_error
&
e
)
{
return
py
::
str
(
e
.
what
()).
release
().
ptr
();
}
return
py
::
str
().
release
().
ptr
();
};
auto
def
=
new
PyMethodDef
{
"f"
,
f
,
METH_VARARGS
,
nullptr
};
return
py
::
reinterpret_steal
<
py
::
object
>
(
PyCFunction_NewEx
(
def
,
nullptr
,
m
.
ptr
()));
}());
}
}
template
<
int
N
>
class
BreaksBase
{};
template
<
int
N
>
class
BreaksBase
{};
...
...
tests/test_class.py
View file @
af2dda38
...
@@ -119,3 +119,11 @@ def test_override_static():
...
@@ -119,3 +119,11 @@ def test_override_static():
assert
isinstance
(
b
,
m
.
MyBase
)
assert
isinstance
(
b
,
m
.
MyBase
)
assert
isinstance
(
d1
,
m
.
MyDerived
)
assert
isinstance
(
d1
,
m
.
MyDerived
)
assert
isinstance
(
d2
,
m
.
MyDerived
)
assert
isinstance
(
d2
,
m
.
MyDerived
)
def
test_implicit_conversion_life_support
():
"""Ensure the lifetime of temporary objects created for implicit conversions"""
assert
m
.
implicitly_convert_argument
(
UserType
(
5
))
==
5
assert
m
.
implicitly_convert_variable
(
UserType
(
5
))
==
5
assert
"outside a bound function"
in
m
.
implicitly_convert_variable_fail
(
UserType
(
5
))
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment