Provide SWIGTYPE MOVE typemaps in swigmove.i

For implementing full move semantics when passing parameters by value.
Based on SWIGTYPE && and std::unique_ptr typemaps which implement move
semantics.

Added for all languages, but untested for: Go, Ocaml, R, Scilab (and
unlikely to be fully functional for same reasons as for std::unique_ptr
support).

Issue #999
This commit is contained in:
William S Fulton 2022-09-16 08:36:25 +01:00
parent de65875955
commit dad7c93ca0
41 changed files with 909 additions and 19 deletions

View File

@ -7,6 +7,10 @@ the issue number to the end of the URL: https://github.com/swig/swig/issues/
Version 4.1.0 (in progress)
===========================
2022-09-16: wsfulton
#999 Provide SWIGTYPE MOVE typemaps in swigmove.i for implementing full
move semantics when passing parameters by value.
2022-08-31: wsfulton
#999 Improve move semantics when using rvalue references.
The SWIGTYPE && input typemaps now assume the object has been moved.

View File

@ -18,7 +18,7 @@
<ul>
<li><a href="#CPlusPlus11_rvalue_reference_inputs">Rvalue reference inputs</a>
<li><a href="#CPlusPlus11_rvalue_reference_outputs">Rvalue reference outputs</a>
<li><a href="#CPlusPlus11_move_only">Movable and move-only types</a>
<li><a href="#CPlusPlus11_move_only">Movable and move-only types by value</a>
</ul>
<li><a href="#CPlusPlus11_generalized_constant_expressions">Generalized constant expressions</a>
<li><a href="#CPlusPlus11_extern_template">Extern template</a>
@ -240,7 +240,7 @@ Another alternative would be to modify the output rvalue reference typemap to al
Fortunately you're highly unlikely to have to solve any of these issues!
</p>
<H4><a name="CPlusPlus11_move_only">7.2.1.3 Movable and move-only types</a></H4>
<H4><a name="CPlusPlus11_move_only">7.2.1.3 Movable and move-only types by value</a></H4>
<p>
@ -252,7 +252,10 @@ Movable types can appear in function signatures for passing 'by value' and in C+
</p>
<p>
SWIG has been enhanced with support for both copyable and/or movable types but this is currently just for function return values.
SWIG has support for both copyable and/or movable types.
Support for move semantics is quite seamless when returning by value from a function.
Support for move semantics is less so and may require some customisation when passing by value to a function.
First let's consider returning by value from a function.
</p>
<p>
@ -283,6 +286,7 @@ struct MoveOnly {
MoveOnly &amp; operator=(MoveOnly &amp;&amp;) = default;
static MoveOnly create() { return MoveOnly(); }
static void take(MoveOnly mo);
};
</pre></div>
@ -303,17 +307,193 @@ SWIGEXPORT void * SWIGSTDCALL CSharp_MoveOnly_create() {
<p>
<tt>SwigValueWrapper</tt> is covered in <a href="SWIGPlus.html#SWIGPlus_nn19">Pass and return by value</a>.
Note that the generated code could be optimised further using the <a href="Typemaps.html#Typemaps_optimal">"optimal" attribute</a> in the "out" typemap.
Note that the generated code could be optimised further using the <a href="Typemaps.html#Typemaps_optimal">"optimal" attribute</a>
in the "out" typemap, so if the above typemap is customised as follows (note that this is C# specific):
</p>
<div class="code"><pre>
%typemap(out, optimal="1") MoveOnly %{
$result = new $1_ltype($1);
%}
</pre></div>
<p>
then the generated code will result in the object being optimally moved:
</p>
<div class="code"><pre>
SWIGEXPORT void * SWIGSTDCALL CSharp_MoveOnly_create() {
void * jresult ;
jresult = new MoveOnly(MoveOnly::create());
return jresult;
}
</pre></div>
<p>
Now let's consider passing by value.
We'll consider three cases; namely types that are:
</p>
<ol>
<li> Copyable and not movable - <tt>CopyOnly</tt>.</li>
<li> Copyable and movable - <tt>MovableCopyable</tt>.</li>
<li> Movable and not copyable - <tt>MoveOnly</tt>.</li>
</ol>
<p>
and for clarification, define these two additional types as follows:
</p>
<div class="code"><pre>
struct CopyOnly {
int val;
CopyOnly(): val(0) {}
CopyOnly(const CopyOnly &amp;) = default;
CopyOnly &amp; operator=(const CopyOnly &amp;) = default;
static CopyOnly create() { return CopyOnly(); }
static void take(CopyOnly co);
};
struct MovableCopyable {
int val;
MovableCopyable(): val(0) {}
MovableCopyable(const MovableCopyable &amp;) = default;
MovableCopyable(MovableCopyable &amp;&amp;) = default;
MovableCopyable &amp; operator=(const MovableCopyable &amp;) = default;
MovableCopyable &amp; operator=(MovableCopyable &amp;&amp;) = default;
static MovableCopyable create() { return MovableCopyable(); }
static void take(MovableCopyable mc);
};
</pre></div>
<p>
The generated code is shown below for <tt>CopyOnly::take</tt> (with additional comments for when constructors and assignment operators are called).
While the code shown is C# specific, the generated constructor and/or assignment operator calls are ultimately the same for all target languages.
</p>
<div class="code"><pre>
SWIGEXPORT void SWIGSTDCALL CSharp_CopyOnly_take(void * jarg1) {
CopyOnly arg1 ; // (a) Default constructor
CopyOnly *argp1 ;
argp1 = (CopyOnly *)jarg1;
if (!argp1) {
SWIG_CSharpSetPendingExceptionArgument(SWIG_CSharpArgumentNullException, "Attempt to dereference null CopyOnly", 0);
return ;
}
arg1 = *argp1; // (b) Copy assignment
CopyOnly::take(SWIG_STD_MOVE(arg1)); // (c) Copy constructor
}
</pre></div>
<p>
Note that <tt>SWIG_STD_MOVE</tt> is a macro defined as shown below to use <tt>std::move</tt> which is only available from C++11 onwards:
</p>
<div class="code"><pre>
#if __cplusplus &gt;=201103L
# define SWIG_STD_MOVE(OBJ) std::move(OBJ)
#else
# define SWIG_STD_MOVE(OBJ) OBJ
#endif
</pre></div>
<p>
Also note: <i>(c) Copy constructor</i>.
Yes, when passing by value the copy constructor is called for all versions of C++, even C++11 and later even though std::move is specified.
It's a C++ language feature for types that don't have move semantics!
</p>
<p>
There is currently only partial support for move-only types as
support for move-only types used as a parameter in a function, that are passed 'by value', is not yet available.
The generated code for <tt>MovableCopyable::take</tt> is the same as for <tt>CopyOnly::take</tt>, however, the C++ compiler will choose the move constructor this time where commented <i>(c) Move constructor</i>:
</p>
<div class="code"><pre>
SWIGEXPORT void SWIGSTDCALL CSharp_MovableCopyable_take(void * jarg1) {
MovableCopyable arg1 ; // (a) Default constructor
MovableCopyable *argp1 ;
argp1 = (MovableCopyable *)jarg1;
if (!argp1) {
SWIG_CSharpSetPendingExceptionArgument(SWIG_CSharpArgumentNullException, "Attempt to dereference null MovableCopyable", 0);
return ;
}
arg1 = *argp1; // (b) Copy assignment
MovableCopyable::take(SWIG_STD_MOVE(arg1)); // (c) Move constructor
}
</pre></div>
<p>
There are two optimisation opportunities available.
</p>
<ol>
<li> Remove the default constructor call with the <tt>%feature("valuewrapper")</tt> covered in <a href="SWIGPlus.html#SWIGPlus_nn19">Pass and return by value</a> and replace it with <tt>SwigValueWrapper</tt>.
</li>
<li> Apply the SWIGTYPE MOVE typemaps which are designed specifically to implement full move semantics when passing parameters by value.
They replace the copy assignment with a call to <tt>SwigValueWrapper::reset</tt>, which works much like <tt>std::unique_ptr::reset</tt>.
These typemaps could alternatively have replaced the copy assignment with a move assignment, but this is not maximally optimal.
</li>
</ol>
<p>
Simply add the following before the <tt>MovableCopyable::take</tt> method is parsed:
</p>
<div class="code"><pre>
%valuewrapper MovableCopyable;
%include &lt;swigmove.i&gt;
%apply SWIGTYPE MOVE { MovableCopyable }
</pre></div>
<p>
will result in this optimal code where just one move constructor is invoked:
</p>
<div class="code"><pre>
SWIGEXPORT void SWIGSTDCALL CSharp_MovableCopyable_take(void * jarg1) {
SwigValueWrapper&lt; MovableCopyable &gt; arg1 ; // (a) No constructors invoked
MovableCopyable *argp1 ;
argp1 = (MovableCopyable *)jarg1;
if (!argp1) {
SWIG_CSharpSetPendingExceptionArgument(SWIG_CSharpArgumentNullException, "Attempt to dereference null MovableCopyable", 0);
return ;
}
SwigValueWrapper&lt; MovableCopyable &gt;::reset(arg1, argp1); // (b) No constructor or assignment operator invoked
MovableCopyable::take(SWIG_STD_MOVE(arg1)); // (c) Move constructor
}
</pre></div>
<p>
Note that <tt>SwigValueWrapper</tt> will call the destructor for the pointer passed to it in the <tt>reset</tt> function.
This pointer is the underlying C++ object that the proxy class owns.
The details aren't shown, but the 'csin' typemap also generates C# code to ensure that the proxy class releases ownership of the object.
Please see the 'SWIGTYPE MOVE' typemaps in the swigmove.i file provided for each target language.
Therefore full move semantics are implemented; ownership is moved from the proxy class into the C++ layer and the net effect
is the same as using an <a href="#CPlusPlus11_rvalue_reference_inputs">rvalue reference parameter</a> discussed earlier.
</p>
<p>
Lastly, let's consider the <tt>MoveOnly::take</tt> function defined earlier.
By default the generated code fails to compile as <tt>MoveOnly</tt> does not have a copy assignment operator.
SWIG is not designed to select a different typemap automatically for move-only types and the user
must apply the SWIGTYPE MOVE typemaps to ensure that only move-only semantics are used.
However, SWIG is able to automatically use <tt>%feature("valuewrapper")</tt> for move-only
types so it is not necessary to explicitly use this feature.
So in this move-only case, simply add the following before <tt>MoveOnly::take</tt> is parsed, which results in the same optimal code shown above for <tt>MovableCopyable</tt>:
</p>
<div class="code"><pre>
%include &lt;swigmove.i&gt;
%apply SWIGTYPE MOVE { MoveOnly }
</pre></div>
<p>
<b>Compatibility note:</b>
SWIG-4.1.0 introduced support for taking advantage of types with move semantics and wrapping functions that return movable or move-only types 'by value'.
SWIG-4.1.0 introduced support for taking advantage of types with move semantics and making it possible to easily use move only types.
</p>

View File

@ -300,7 +300,7 @@
<ul>
<li><a href="CPlusPlus11.html#CPlusPlus11_rvalue_reference_inputs">Rvalue reference inputs</a>
<li><a href="CPlusPlus11.html#CPlusPlus11_rvalue_reference_outputs">Rvalue reference outputs</a>
<li><a href="CPlusPlus11.html#CPlusPlus11_move_only">Movable and move-only types</a>
<li><a href="CPlusPlus11.html#CPlusPlus11_move_only">Movable and move-only types by value</a>
</ul>
<li><a href="CPlusPlus11.html#CPlusPlus11_generalized_constant_expressions">Generalized constant expressions</a>
<li><a href="CPlusPlus11.html#CPlusPlus11_extern_template">Extern template</a>

View File

@ -597,8 +597,9 @@ CPP11_TEST_CASES += \
cpp11_initializer_list \
cpp11_initializer_list_extend \
cpp11_lambda_functions \
cpp11_move_only \
cpp11_move_only_valuewrapper \
cpp11_move_only \
cpp11_move_typemaps \
cpp11_move_only_valuewrapper \
cpp11_noexcept \
cpp11_null_pointer_constant \
cpp11_raw_string_literals \

View File

@ -12,19 +12,23 @@ using namespace std;
bool trace = false;
struct MoveOnly {
MoveOnly(int i = 0) { if (trace) cout << "MoveOnly(" << i << ")" << " " << this << endl; Counter::normal_constructor++; }
int val;
MoveOnly(int i = 0) : val(i) { if (trace) cout << "MoveOnly(" << i << ")" << " " << this << endl; Counter::normal_constructor++; }
MoveOnly(const MoveOnly &other) = delete;
MoveOnly & operator=(const MoveOnly &other) = delete;
MoveOnly(MoveOnly &&other) noexcept { if (trace) cout << "MoveOnly(MoveOnly &&)" << " " << this << endl; Counter::move_constructor++; }
MoveOnly & operator=(MoveOnly &&other) noexcept { if (trace) cout << "operator=(MoveOnly &&)" << " " << this << endl; Counter::move_assignment++; return *this; }
MoveOnly(MoveOnly &&other) noexcept : val(std::move(other.val)) { if (trace) cout << "MoveOnly(MoveOnly &&)" << " " << this << endl; Counter::move_constructor++; }
MoveOnly & operator=(MoveOnly &&other) noexcept { if (trace) cout << "operator=(MoveOnly &&)" << " " << this << endl; Counter::move_assignment++; if (this != &other) { val = std::move(other.val); } return *this; }
~MoveOnly() { if (trace) cout << "~MoveOnly()" << " " << this << endl; Counter::destructor++; }
static MoveOnly create() { return MoveOnly(111); }
// static const MoveOnly createConst() { return MoveOnly(111); } // not supported by default
// static void take(MoveOnly mo) { if (trace) cout << "take(MoveOnly)" << " " << &mo << endl; }
// compile error by default, see cpp11_move_typemaps.i
#if defined(WRAP_TAKE_METHOD)
static void take(MoveOnly mo) { if (trace) cout << "take(MoveOnly)" << " " << &mo << endl; }
#endif
};
%}
@ -35,13 +39,14 @@ struct MoveOnly {
%inline %{
// Movable and Copyable
struct MovableCopyable {
MovableCopyable(int i = 0) { if (trace) cout << "MovableCopyable(" << i << ")" << " " << this << endl; Counter::normal_constructor++; }
int val;
MovableCopyable(int i = 0) : val(i) { if (trace) cout << "MovableCopyable(" << i << ")" << " " << this << endl; Counter::normal_constructor++; }
MovableCopyable(const MovableCopyable &other) { if (trace) cout << "MovableCopyable(const MovableCopyable &)" << " " << this << " " << &other << endl; Counter::copy_constructor++;}
MovableCopyable & operator=(const MovableCopyable &other) { if (trace) cout << "operator=(const MovableCopyable &)" << " " << this << " " << &other << endl; Counter::copy_assignment++; return *this; }
MovableCopyable(const MovableCopyable &other) : val(other.val) { if (trace) cout << "MovableCopyable(const MovableCopyable &)" << " " << this << " " << &other << endl; Counter::copy_constructor++;}
MovableCopyable & operator=(const MovableCopyable &other) { if (trace) cout << "operator=(const MovableCopyable &)" << " " << this << " " << &other << endl; Counter::copy_assignment++; if (this != &other) { val = other.val; } return *this; }
MovableCopyable(MovableCopyable &&other) noexcept { if (trace) cout << "MovableCopyable(MovableCopyable &&)" << " " << this << endl; Counter::move_constructor++; }
MovableCopyable & operator=(MovableCopyable &&other) noexcept { if (trace) cout << "operator=(MovableCopyable &&)" << " " << this << endl; Counter::move_assignment++; return *this; }
MovableCopyable(MovableCopyable &&other) noexcept : val(std::move(other.val)) { if (trace) cout << "MovableCopyable(MovableCopyable &&)" << " " << this << endl; Counter::move_constructor++; }
MovableCopyable & operator=(MovableCopyable &&other) noexcept { if (trace) cout << "operator=(MovableCopyable &&)" << " " << this << endl; Counter::move_assignment++; if (this != &other) { val = std::move(other.val); } return *this; }
~MovableCopyable() { if (trace) cout << "~MovableCopyable()" << " " << this << endl; Counter::destructor++; }
static MovableCopyable create() { return MovableCopyable(111); }

View File

@ -0,0 +1,12 @@
%module cpp11_move_typemaps
%include <swigmove.i>
%apply SWIGTYPE MOVE { MoveOnly mo }
%valuewrapper MovableCopyable;
%apply SWIGTYPE MOVE { MovableCopyable mc }
%inline %{
#define WRAP_TAKE_METHOD
%}
%include "cpp11_move_only.i"

View File

@ -0,0 +1,37 @@
using System;
using cpp11_move_typemapsNamespace;
public class cpp11_move_typemaps_runme {
public static void Main() {
Counter.reset_counts();
using (MoveOnly mo = new MoveOnly(111)) {
Counter.check_counts(1, 0, 0, 0, 0, 0);
MoveOnly.take(mo);
Counter.check_counts(1, 0, 0, 1, 0, 2);
}
Counter.check_counts(1, 0, 0, 1, 0, 2);
Counter.reset_counts();
using (MovableCopyable mo = new MovableCopyable(111)) {
Counter.check_counts(1, 0, 0, 0, 0, 0);
MovableCopyable.take(mo);
Counter.check_counts(1, 0, 0, 1, 0, 2);
}
Counter.check_counts(1, 0, 0, 1, 0, 2);
using (MoveOnly mo = new MoveOnly(222)) {
MoveOnly.take(mo);
bool exception_thrown = false;
try {
MoveOnly.take(mo);
} catch (ApplicationException e) {
if (!e.Message.Contains("Cannot release ownership as memory is not owned"))
throw new ApplicationException("incorrect exception message");
exception_thrown = true;
}
if (!exception_thrown)
throw new ApplicationException("double usage of take should have been an error");
}
}
}

View File

@ -0,0 +1,42 @@
module cpp11_move_typemaps_runme;
import cpp11_move_typemaps.Counter;
import cpp11_move_typemaps.MoveOnly;
import cpp11_move_typemaps.MovableCopyable;
import std.conv;
import std.algorithm;
void main() {
{
Counter.reset_counts();
scope MoveOnly mo = new MoveOnly(111);
Counter.check_counts(1, 0, 0, 0, 0, 0);
MoveOnly.take(mo);
Counter.check_counts(1, 0, 0, 1, 0, 2);
}
Counter.check_counts(1, 0, 0, 1, 0, 2);
{
Counter.reset_counts();
scope MovableCopyable mo = new MovableCopyable(111);
Counter.check_counts(1, 0, 0, 0, 0, 0);
MovableCopyable.take(mo);
Counter.check_counts(1, 0, 0, 1, 0, 2);
}
Counter.check_counts(1, 0, 0, 1, 0, 2);
{
scope MoveOnly mo = new MoveOnly(222);
MoveOnly.take(mo);
bool exception_thrown = false;
try {
MoveOnly.take(mo);
} catch (Exception e) {
if (!canFind(e.msg, "Cannot release ownership as memory is not owned"))
throw new Exception("incorrect exception message: " ~ e.msg);
exception_thrown = true;
}
if (!exception_thrown)
throw new Exception("double usage of take should have been an error");
}
}

View File

@ -0,0 +1,42 @@
module cpp11_move_typemaps_runme;
import cpp11_move_typemaps.Counter;
import cpp11_move_typemaps.MoveOnly;
import cpp11_move_typemaps.MovableCopyable;
import std.conv;
import std.algorithm;
void main() {
{
Counter.reset_counts();
scope MoveOnly mo = new MoveOnly(111);
Counter.check_counts(1, 0, 0, 0, 0, 0);
MoveOnly.take(mo);
Counter.check_counts(1, 0, 0, 1, 0, 2);
}
Counter.check_counts(1, 0, 0, 1, 0, 2);
{
Counter.reset_counts();
scope MovableCopyable mo = new MovableCopyable(111);
Counter.check_counts(1, 0, 0, 0, 0, 0);
MovableCopyable.take(mo);
Counter.check_counts(1, 0, 0, 1, 0, 2);
}
Counter.check_counts(1, 0, 0, 1, 0, 2);
{
scope MoveOnly mo = new MoveOnly(222);
MoveOnly.take(mo);
bool exception_thrown = false;
try {
MoveOnly.take(mo);
} catch (Exception e) {
if (!canFind(e.msg, "Cannot release ownership as memory is not owned"))
throw new Exception("incorrect exception message: " ~ e.msg);
exception_thrown = true;
}
if (!exception_thrown)
throw new Exception("double usage of take should have been an error");
}
}

View File

@ -0,0 +1,3 @@
(dynamic-call "scm_init_cpp11_move_typemaps_module" (dynamic-link "./libcpp11_move_typemaps"))
(load "testsuite.scm")
(load "../schemerunme/cpp11_move_typemaps.scm")

View File

@ -0,0 +1,51 @@
import cpp11_move_typemaps.*;
public class cpp11_move_typemaps_runme {
static {
try {
System.loadLibrary("cpp11_move_typemaps");
} catch (UnsatisfiedLinkError e) {
System.err.println("Native code library failed to load. See the chapter on Dynamic Linking Problems in the SWIG Java documentation for help.\n" + e);
System.exit(1);
}
}
public static void main(String argv[]) {
{
Counter.reset_counts();
MoveOnly mo = new MoveOnly(111);
Counter.check_counts(1, 0, 0, 0, 0, 0);
MoveOnly.take(mo);
Counter.check_counts(1, 0, 0, 1, 0, 2);
mo.delete();
}
Counter.check_counts(1, 0, 0, 1, 0, 2);
{
Counter.reset_counts();
MovableCopyable mo = new MovableCopyable(111);
Counter.check_counts(1, 0, 0, 0, 0, 0);
MovableCopyable.take(mo);
Counter.check_counts(1, 0, 0, 1, 0, 2);
mo.delete();
}
Counter.check_counts(1, 0, 0, 1, 0, 2);
{
MoveOnly mo = new MoveOnly(222);
MoveOnly.take(mo);
boolean exception_thrown = false;
try {
MoveOnly.take(mo);
} catch (RuntimeException e) {
if (!e.getMessage().contains("Cannot release ownership as memory is not owned"))
throw new RuntimeException("incorrect exception message");
exception_thrown = true;
}
if (!exception_thrown)
throw new RuntimeException("double usage of take should have been an error");
}
}
}

View File

@ -0,0 +1,30 @@
var cpp11_move_typemaps = require("cpp11_move_typemaps");
cpp11_move_typemaps.Counter.reset_counts();
mo = new cpp11_move_typemaps.MoveOnly(111);
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 0, 0, 0);
cpp11_move_typemaps.MoveOnly.take(mo);
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 1, 0, 2);
delete mo;
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 1, 0, 2);
cpp11_move_typemaps.Counter.reset_counts();
mo = new cpp11_move_typemaps.MovableCopyable(111);
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 0, 0, 0);
cpp11_move_typemaps.MovableCopyable.take(mo);
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 1, 0, 2);
delete mo;
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 1, 0, 2);
mo = new cpp11_move_typemaps.MoveOnly(222);
cpp11_move_typemaps.MoveOnly.take(mo);
exception_thrown = false;
try {
cpp11_move_typemaps.MoveOnly.take(mo);
} catch (e) {
if (!e.message.includes("cannot release ownership as memory is not owned"))
throw new Error("incorrect exception message:" + e.message);
exception_thrown = true;
}
if (!exception_thrown)
throw new Error("double usage of take should have been an error");

View File

@ -0,0 +1,28 @@
require("import") -- the import fn
import("cpp11_move_typemaps") -- import code
-- catch "undefined" global variables
local env = _ENV -- Lua 5.2
if not env then env = getfenv () end -- Lua 5.1
setmetatable(env, {__index=function (t,i) error("undefined global variable `"..i.."'",2) end})
cpp11_move_typemaps.Counter.reset_counts()
mo = cpp11_move_typemaps.MoveOnly(111)
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 0, 0, 0)
cpp11_move_typemaps.MoveOnly.take(mo)
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 1, 0, 2)
mo = nil
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 1, 0, 2)
cpp11_move_typemaps.Counter.reset_counts()
mo = cpp11_move_typemaps.MovableCopyable(111)
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 0, 0, 0)
cpp11_move_typemaps.MovableCopyable.take(mo)
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 1, 0, 2)
mo = nil
cpp11_move_typemaps.Counter.check_counts(1, 0, 0, 1, 0, 2)
mo = cpp11_move_typemaps.MoveOnly(222)
cpp11_move_typemaps.MoveOnly.take(mo)
s, msg = pcall(function() cpp11_move_typemaps.MoveOnly.take(mo) end)
assert(s == false and msg:find("Cannot release ownership as memory is not owned", 1, true))

View File

@ -0,0 +1,35 @@
(load-extension "cpp11_move_typemaps.so")
(require (lib "defmacro.ss"))
; Copied from ../schemerunme/cpp11_move_typemaps.scm and modified for exceptions
; Define an equivalent to Guile's gc procedure
(define-macro (gc)
`(collect-garbage 'major))
(Counter-reset-counts)
(define mo (new-MoveOnly 111))
(Counter-check-counts 1 0 0 0 0 0)
(MoveOnly-take mo)
(Counter-check-counts 1 0 0 1 0 2)
(delete-MoveOnly mo)
(Counter-check-counts 1 0 0 1 0 2)
(Counter-reset-counts)
(define mo (new-MovableCopyable 111))
(Counter-check-counts 1 0 0 0 0 0)
(MovableCopyable-take mo)
(Counter-check-counts 1 0 0 1 0 2)
(delete-MovableCopyable mo)
(Counter-check-counts 1 0 0 1 0 2)
(define mo (new-MoveOnly 222))
(MoveOnly-take mo)
(define exception_thrown "no exception thrown for mo")
(with-handlers ([exn:fail? (lambda (exn)
(set! exception_thrown (exn-message exn)))])
(MoveOnly-take mo))
(unless (string-contains? exception_thrown "cannot release ownership as memory is not owned")
(error "Wrong or no exception thrown: " exception_thrown))
(exit 0)

View File

@ -0,0 +1,37 @@
# do not dump Octave core
if exist("crash_dumps_octave_core", "builtin")
crash_dumps_octave_core(0);
endif
cpp11_move_typemaps
Counter.reset_counts();
mo = MoveOnly(111);
Counter_check_counts(1, 0, 0, 0, 0, 0);
MoveOnly.take(mo);
Counter_check_counts(1, 0, 0, 1, 0, 2);
clear mo;
Counter_check_counts(1, 0, 0, 1, 0, 2);
Counter.reset_counts();
mo = MovableCopyable(111);
Counter_check_counts(1, 0, 0, 0, 0, 0);
MovableCopyable.take(mo);
Counter_check_counts(1, 0, 0, 1, 0, 2);
clear mo;
Counter_check_counts(1, 0, 0, 1, 0, 2);
mo = MoveOnly(222);
MoveOnly.take(mo);
exception_thrown = false;
try
MoveOnly.take(mo);
catch e
if (isempty(strfind(e.message, "cannot release ownership as memory is not owned")))
error("incorrect exception message %s", e.message);
endif
exception_thrown = true;
end_try_catch
if (!exception_thrown)
error("double usage of take should have been an error");
endif

View File

@ -0,0 +1,34 @@
use strict;
use warnings;
use Test::More tests => 3;
BEGIN { use_ok('cpp11_move_typemaps') }
require_ok('cpp11_move_typemaps');
{
cpp11_move_typemaps::Counter::reset_counts();
my $mo = new cpp11_move_typemaps::MoveOnly(111);
cpp11_move_typemaps::Counter::check_counts(1, 0, 0, 0, 0, 0);
cpp11_move_typemaps::MoveOnly::take($mo);
cpp11_move_typemaps::Counter::check_counts(1, 0, 0, 1, 0, 2);
undef $mo;
}
cpp11_move_typemaps::Counter::check_counts(1, 0, 0, 1, 0, 2);
{
cpp11_move_typemaps::Counter::reset_counts();
my $mo = new cpp11_move_typemaps::MovableCopyable(111);
cpp11_move_typemaps::Counter::check_counts(1, 0, 0, 0, 0, 0);
cpp11_move_typemaps::MovableCopyable::take($mo);
cpp11_move_typemaps::Counter::check_counts(1, 0, 0, 1, 0, 2);
undef $mo;
}
cpp11_move_typemaps::Counter::check_counts(1, 0, 0, 1, 0, 2);
{
my $mo = new cpp11_move_typemaps::MoveOnly(222);
cpp11_move_typemaps::MoveOnly::take($mo);
eval {
cpp11_move_typemaps::MoveOnly::take($mo);
};
like($@, qr/\bcannot release ownership as memory is not owned\b/, "double usage of takeKlassUniquePtr should be an error");
}

View File

@ -0,0 +1,32 @@
<?php
require "tests.php";
Counter::reset_counts();
$mo = new MoveOnly(111);
Counter::check_counts(1, 0, 0, 0, 0, 0);
MoveOnly::take($mo);
Counter::check_counts(1, 0, 0, 1, 0, 2);
$mo = NULL;
Counter::check_counts(1, 0, 0, 1, 0, 2);
Counter::reset_counts();
$mo = new MovableCopyable(111);
Counter::check_counts(1, 0, 0, 0, 0, 0);
MovableCopyable::take($mo);
Counter::check_counts(1, 0, 0, 1, 0, 2);
$mo = NULL;
Counter::check_counts(1, 0, 0, 1, 0, 2);
$mo = new MoveOnly(222);
MoveOnly::take($mo);
$exception_thrown = false;
try {
MoveOnly::take($mo);
} catch (TypeError $e) {
check::str_contains($e->getMessage(), "Cannot release ownership as memory is not owned", "incorrect exception message: {$e->getMessage()}");
$exception_thrown = true;
}
check::equal($exception_thrown, true, "double usage of takeKlassUniquePtr should have been an error");
check::done();

View File

@ -0,0 +1,29 @@
from cpp11_move_typemaps import *
Counter.reset_counts()
mo = MoveOnly(111)
Counter.check_counts(1, 0, 0, 0, 0, 0)
MoveOnly.take(mo)
Counter.check_counts(1, 0, 0, 1, 0, 2)
del mo
Counter.check_counts(1, 0, 0, 1, 0, 2)
Counter.reset_counts()
mo = MovableCopyable(111)
Counter.check_counts(1, 0, 0, 0, 0, 0)
MovableCopyable.take(mo)
Counter.check_counts(1, 0, 0, 1, 0, 2)
del mo
Counter.check_counts(1, 0, 0, 1, 0, 2)
mo = MoveOnly(222)
MoveOnly.take(mo)
exception_thrown = False
try:
MoveOnly.take(mo)
except RuntimeError as e:
if "cannot release ownership as memory is not owned" not in str(e):
raise RuntimeError("incorrect exception message:" + str(e))
exception_thrown = True
if not exception_thrown:
raise RuntimeError("Should have thrown 'Cannot release ownership as memory is not owned' error")

View File

@ -0,0 +1,36 @@
#!/usr/bin/env ruby
require 'swig_assert'
require 'cpp11_move_typemaps'
Cpp11_move_typemaps::Counter.reset_counts()
mo = Cpp11_move_typemaps::MoveOnly.new(111)
Cpp11_move_typemaps::Counter.check_counts(1, 0, 0, 0, 0, 0)
Cpp11_move_typemaps::MoveOnly.take(mo)
Cpp11_move_typemaps::Counter.check_counts(1, 0, 0, 1, 0, 2)
mo = nil
Cpp11_move_typemaps::Counter.check_counts(1, 0, 0, 1, 0, 2)
Cpp11_move_typemaps::Counter.reset_counts()
mo = Cpp11_move_typemaps::MovableCopyable.new(111)
Cpp11_move_typemaps::Counter.check_counts(1, 0, 0, 0, 0, 0)
Cpp11_move_typemaps::MovableCopyable.take(mo)
Cpp11_move_typemaps::Counter.check_counts(1, 0, 0, 1, 0, 2)
mo = nil
Cpp11_move_typemaps::Counter.check_counts(1, 0, 0, 1, 0, 2)
mo = Cpp11_move_typemaps::MoveOnly.new(222)
Cpp11_move_typemaps::MoveOnly.take(mo)
exception_thrown = false
begin
Cpp11_move_typemaps::MoveOnly.take(mo)
rescue RuntimeError => e
if (!e.to_s.include? "cannot release ownership as memory is not owned")
raise RuntimeError, "incorrect exception message: #{e.to_s}"
end
exception_thrown = true
end
if (!exception_thrown)
raise RuntimeError, "Should have thrown 'Cannot release ownership as memory is not owned' error"
end

View File

@ -0,0 +1,23 @@
(Counter-reset-counts)
(define mo (new-MoveOnly 111))
(Counter-check-counts 1 0 0 0 0 0)
(MoveOnly-take mo)
(Counter-check-counts 1 0 0 1 0 2)
(delete-MoveOnly mo)
(Counter-check-counts 1 0 0 1 0 2)
(Counter-reset-counts)
(define mo (new-MovableCopyable 111))
(Counter-check-counts 1 0 0 0 0 0)
(MovableCopyable-take mo)
(Counter-check-counts 1 0 0 1 0 2)
(delete-MovableCopyable mo)
(Counter-check-counts 1 0 0 1 0 2)
(define mo (new-MoveOnly 222))
(MoveOnly-take mo)
(expect-throw 'misc-error
(MoveOnly-take mo))
; TODO: check the exception message
(exit 0)

View File

@ -0,0 +1,35 @@
if [ catch { load ./cpp11_move_typemaps[info sharedlibextension] cpp11_move_typemaps} err_msg ] {
puts stderr "Could not load shared object:\n$err_msg"
}
Counter_reset_counts
MoveOnly mo 111
Counter_check_counts 1 0 0 0 0 0
MoveOnly_take mo
Counter_check_counts 1 0 0 1 0 2
mo -delete
Counter_check_counts 1 0 0 1 0 2
Counter_reset_counts
MovableCopyable mo 111
Counter_check_counts 1 0 0 0 0 0
MovableCopyable_take mo
Counter_check_counts 1 0 0 1 0 2
mo -delete
Counter_check_counts 1 0 0 1 0 2
MoveOnly mo 222
MoveOnly_take mo
set exception_thrown 0
if [ catch {
MoveOnly_take mo
} e ] {
if {[string first "cannot release ownership as memory is not owned" $e] == -1} {
error "incorrect exception message: $e"
}
set exception_thrown 1
}
if {!$exception_thrown} {
error "Should have thrown 'Cannot release ownership as memory is not owned' error"
}

16
Lib/csharp/swigmove.i Normal file
View File

@ -0,0 +1,16 @@
/* -----------------------------------------------------------------------------
* swigmove.i
*
* Input typemaps library for implementing full move semantics when passing
* parameters by value.
* ----------------------------------------------------------------------------- */
%typemap(in, canthrow=1, fragment="<memory>") SWIGTYPE MOVE ($&1_type argp)
%{ argp = ($&1_ltype)$input;
if (!argp) {
SWIG_CSharpSetPendingExceptionArgument(SWIG_CSharpArgumentNullException, "Attempt to dereference null $1_type", 0);
return $null;
}
SwigValueWrapper< $1_ltype >::reset($1, argp); %}
%typemap(csin) SWIGTYPE MOVE "$&csclassname.swigRelease($csinput)"

16
Lib/d/swigmove.i Normal file
View File

@ -0,0 +1,16 @@
/* -----------------------------------------------------------------------------
* swigmove.i
*
* Input typemaps library for implementing full move semantics when passing
* parameters by value.
* ----------------------------------------------------------------------------- */
%typemap(in, canthrow=1) SWIGTYPE MOVE ($&1_type argp)
%{ argp = ($&1_ltype)$input;
if (!argp) {
SWIG_DSetPendingException(SWIG_DIllegalArgumentException, "Attempt to dereference null $1_type");
return $null;
}
SwigValueWrapper< $1_ltype >::reset($1, argp); %}
%typemap(din) SWIGTYPE MOVE "$dclassname.swigRelease($dinput)"

15
Lib/go/swigmove.i Normal file
View File

@ -0,0 +1,15 @@
/* -----------------------------------------------------------------------------
* swigmove.i
*
* Input typemaps library for implementing full move semantics when passing
* parameters by value.
* ----------------------------------------------------------------------------- */
%typemap(in) SWIGTYPE MOVE ($&1_type argp)
%{
argp = ($&1_ltype)$input;
if (argp == NULL) {
_swig_gopanic("Attempt to dereference null $1_type");
}
SwigValueWrapper< $1_ltype >::reset($1, argp);
%}

19
Lib/guile/swigmove.i Normal file
View File

@ -0,0 +1,19 @@
/* -----------------------------------------------------------------------------
* swigmove.i
*
* Input typemaps library for implementing full move semantics when passing
* parameters by value.
* ----------------------------------------------------------------------------- */
%typemap(in, noblock=1) SWIGTYPE MOVE (void *argp = 0, int res = 0) {
res = SWIG_ConvertPtr($input, &argp, $&1_descriptor, SWIG_POINTER_RELEASE);
if (!SWIG_IsOK(res)) {
if (res == SWIG_ERROR_RELEASE_NOT_OWNED) {
%releasenotowned_fail(res, "$1_type", $symname, $argnum);
} else {
%argument_fail(res, "$1_type", $symname, $argnum);
}
}
if (!argp) { %argument_nullref("$1_type", $symname, $argnum); }
SwigValueWrapper< $1_ltype >::reset($1, ($&1_type)argp);
}

16
Lib/java/swigmove.i Normal file
View File

@ -0,0 +1,16 @@
/* -----------------------------------------------------------------------------
* swigmove.i
*
* Input typemaps library for implementing full move semantics when passing
* parameters by value.
* ----------------------------------------------------------------------------- */
%typemap(in) SWIGTYPE MOVE ($&1_type argp)
%{ argp = *($&1_ltype*)&$input;
if (!argp) {
SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "Attempt to dereference null $1_type");
return $null;
}
SwigValueWrapper< $1_ltype >::reset($1, argp); %}
%typemap(javain) SWIGTYPE MOVE "$&javaclassname.swigRelease($javainput)"

View File

@ -0,0 +1 @@
%include <typemaps/swigmove.swg>

View File

@ -0,0 +1 @@
%include <typemaps/swigmove.swg>

18
Lib/lua/swigmove.i Normal file
View File

@ -0,0 +1,18 @@
/* -----------------------------------------------------------------------------
* swigmove.i
*
* Input typemaps library for implementing full move semantics when passing
* parameters by value.
* ----------------------------------------------------------------------------- */
%typemap(in, checkfn="lua_isuserdata", noblock=1) SWIGTYPE MOVE (void *argp = 0, int res = 0) {
res = SWIG_ConvertPtr(L, $input, &argp, $&1_descriptor, SWIG_POINTER_RELEASE);
if (!SWIG_IsOK(res)) {
if (res == SWIG_ERROR_RELEASE_NOT_OWNED) {
lua_pushfstring(L, "Cannot release ownership as memory is not owned for argument $argnum of type '$1_type' in $symname"); SWIG_fail;
} else {
SWIG_fail_ptr("$symname", $argnum, $&1_descriptor);
}
}
SwigValueWrapper< $1_ltype >::reset($1, ($&1_type)argp);
}

19
Lib/mzscheme/swigmove.i Normal file
View File

@ -0,0 +1,19 @@
/* -----------------------------------------------------------------------------
* swigmove.i
*
* Input typemaps library for implementing full move semantics when passing
* parameters by value.
* ----------------------------------------------------------------------------- */
%typemap(in, noblock=1) SWIGTYPE MOVE (void *argp = 0, int res = 0) {
res = SWIG_ConvertPtr($input, &argp, $&1_descriptor, SWIG_POINTER_RELEASE);
if (!SWIG_IsOK(res)) {
if (res == SWIG_ERROR_RELEASE_NOT_OWNED) {
scheme_signal_error(FUNC_NAME ": cannot release ownership as memory is not owned for argument $argnum of type '$1_type'");
} else {
%argument_fail(res, "$1_type", $symname, $argnum);
}
}
if (argp == NULL) scheme_signal_error(FUNC_NAME ": swig-type-error (null reference)");
SwigValueWrapper< $1_ltype >::reset($1, ($&1_type)argp);
}

11
Lib/ocaml/swigmove.i Normal file
View File

@ -0,0 +1,11 @@
/* -----------------------------------------------------------------------------
* swigmove.i
*
* Input typemaps library for implementing full move semantics when passing
* parameters by value.
* ----------------------------------------------------------------------------- */
%typemap(in, noblock=1) SWIGTYPE MOVE (void *argp = 0) {
argp1 = ($&1_ltype) caml_ptr_val($input,$&1_descriptor);
SwigValueWrapper< $1_ltype >::reset($1, ($&1_type)argp);
}

1
Lib/octave/swigmove.i Normal file
View File

@ -0,0 +1 @@
%include <typemaps/swigmove.swg>

1
Lib/perl5/swigmove.i Normal file
View File

@ -0,0 +1 @@
%include <typemaps/swigmove.swg>

24
Lib/php/swigmove.i Normal file
View File

@ -0,0 +1,24 @@
/* -----------------------------------------------------------------------------
* swigmove.i
*
* Input typemaps library for implementing full move semantics when passing
* parameters by value.
* ----------------------------------------------------------------------------- */
%typemap(in, noblock=1) SWIGTYPE MOVE (void *argp = 0, int res = 0) {
res = SWIG_ConvertPtr(&$input, &argp, $&1_descriptor, SWIG_POINTER_RELEASE);
if (!SWIG_IsOK(res)) {
if (res == SWIG_ERROR_RELEASE_NOT_OWNED) {
zend_type_error("Cannot release ownership as memory is not owned for argument $argnum of $&1_descriptor of $symname");
return;
} else {
zend_type_error("Expected $&1_descriptor for argument $argnum of $symname");
return;
}
}
if (!argp) {
zend_type_error("Invalid null reference for argument $argnum of $&1_descriptor of $symname");
return;
}
SwigValueWrapper< $1_ltype >::reset($1, ($&1_type)argp);
}

1
Lib/python/swigmove.i Normal file
View File

@ -0,0 +1 @@
%include <typemaps/swigmove.swg>

1
Lib/r/swigmove.i Normal file
View File

@ -0,0 +1 @@
%include <typemaps/swigmove.swg>

1
Lib/ruby/swigmove.i Normal file
View File

@ -0,0 +1 @@
%include <typemaps/swigmove.swg>

1
Lib/scilab/swigmove.i Normal file
View File

@ -0,0 +1 @@
%include <typemaps/swigmove.swg>

View File

@ -656,6 +656,16 @@ namespace std {
* arg1 = *inarg1; // Assignment from a pointer
* arg1 = Vector(1,2,3); // Assignment from a value
*
* SwigValueWrapper is a drop in replacement to modify normal value semantics by
* using the heap instead of the stack to copy/move the underlying object it is
* managing. Smart pointers also manage an underlying object on the heap, so
* SwigValueWrapper has characteristics of a smart pointer. The reset function
* is specific smart pointer functionality, but cannot be a non-static member as
* when SWIG modifies typemap code it assumes non-static member function calls
* are routed to the underlying object, changing for example $1.f() to (&x)->f().
* The reset function was added as an optimisation to avoid some copying/moving
* and to take ownership of an object already created on the heap.
*
* The class offers a strong guarantee of exception safety.
* With regards to the implementation, the private SwigSmartPointer nested class is
* a simple smart pointer providing exception safety, much like std::auto_ptr.
@ -677,6 +687,7 @@ template<typename T> class SwigValueWrapper {
SwigSmartPointer(T *p) : ptr(p) { }
~SwigSmartPointer() { delete ptr; }
SwigSmartPointer& operator=(SwigSmartPointer& rhs) { T* oldptr = ptr; ptr = 0; delete oldptr; ptr = rhs.ptr; rhs.ptr = 0; return *this; }
void reset(T *p) { T* oldptr = ptr; ptr = 0; delete oldptr; ptr = p; }
} pointer;
SwigValueWrapper& operator=(const SwigValueWrapper<T>& rhs);
SwigValueWrapper(const SwigValueWrapper<T>& rhs);
@ -690,6 +701,7 @@ public:
operator T&() const { return *pointer.ptr; }
#endif
T *operator&() const { return pointer.ptr; }
static void reset(SwigValueWrapper& t, T *p) { t.pointer.reset(p); }
};
/*

1
Lib/tcl/swigmove.i Normal file
View File

@ -0,0 +1 @@
%include <typemaps/swigmove.swg>

19
Lib/typemaps/swigmove.swg Normal file
View File

@ -0,0 +1,19 @@
/* -----------------------------------------------------------------------------
* swigmove.swg
*
* Input typemaps library for implementing full move semantics when passing
* parameters by value.
* ----------------------------------------------------------------------------- */
%typemap(in, noblock=1) SWIGTYPE MOVE (void *argp = 0, int res = 0) {
res = SWIG_ConvertPtr($input, &argp, $&1_descriptor, SWIG_POINTER_RELEASE);
if (!SWIG_IsOK(res)) {
if (res == SWIG_ERROR_RELEASE_NOT_OWNED) {
%releasenotowned_fail(res, "$1_type", $symname, $argnum);
} else {
%argument_fail(res, "$1_type", $symname, $argnum);
}
}
if (!argp) { %argument_nullref("$1_type", $symname, $argnum); }
SwigValueWrapper< $1_ltype >::reset($1, ($&1_type)argp);
}