Skip to content

Move arguments instead of copy #1612

@hmenke

Description

@hmenke

Consider the following class in C++. Note that the class is not copy constructable as it contains a managed pointer. The constructor of the function may throw an exception.

#pragma once
#include <iostream>
#include <memory>

class TestClass
{
public:
  std::unique_ptr<int> i;

  TestClass() : i(new int) {};

  TestClass(int i_) : i(new int)
  {
    if ( i_ == 0 )
      throw 0;
    *i = i_;
  }
};

inline void cpp_function(TestClass t)
{
  std::cout << *(t.i) << "\n";
}

This exception should be propagated to Cython, which should be possible via except +.

cdef extern from "test.hpp":
    cppclass TestClass:
        TestClass(int) except +

    void cpp_function(TestClass t)
        
def interface(i):
     cpp_function(TestClass(i))

Unfortunately, the generated code cannot be compiled. Let’s take a look why. The relevant part is this (shortened) bit:

static PyObject *__pyx_pf_4test_interface(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_i) {
  /* ... some more stuff ... */
  TestClass __pyx_t_2;
  __Pyx_RefNannySetupContext("interface", 0);

  /* "test.pyx":8
 * 
 * def interface(i):
 *      cpp_function(TestClass(i))             # <<<<<<<<<<<<<<
 */
  __pyx_t_1 = __Pyx_PyInt_As_int(__pyx_v_i); if (unlikely((__pyx_t_1 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 8, __pyx_L1_error)
  try {
    __pyx_t_2 = TestClass(__pyx_t_1);
  } catch(...) {
    __Pyx_CppExn2PyErr();
    __PYX_ERR(0, 8, __pyx_L1_error)
  }
  cpp_function(__pyx_t_2);
  /* ... some more stuff ... */
}

First of all we notice the line

TestClass __pyx_t_2;

which implies that the object is default constructable. If not, the code will not compile. A meaningless default constructor can in most cases be achieved but puts some additional bookkeeping work on the C++ programmer’s shoulders. This is also an issue but can be circumvented.

More crucial is the line

cpp_function(__pyx_t_2);

where the object is attempted to be copied. This is not possible as the object is not copyable. The code does not compile, because error: use of deleted function ‘TestClass::TestClass(const TestClass&)’.

With C++11 this situation can be handled though, simply using

cpp_function(std::move(__pyx_t_2));

In case that the object can be moved this is done, otherwise it is copied nevertheless.

Please add std::move to function calls.


Addendum:

It would be best to use a pointer for the object to be created in combination with the move. This way it does neither need to be default constructable nor copyable.

static PyObject *__pyx_pf_4test_interface(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_i) {
  /* ... some more stuff ... */
  std::unique_ptr<TestClass> __pyx_t_2;
  __Pyx_RefNannySetupContext("interface", 0);

  /* "test.pyx":8
 * 
 * def interface(i):
 *      cpp_function(TestClass(i))             # <<<<<<<<<<<<<<
 */
  __pyx_t_1 = __Pyx_PyInt_As_int(__pyx_v_i); if (unlikely((__pyx_t_1 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 8, __pyx_L1_error)
  try {
    __pyx_t_2.reset(new TestClass(__pyx_t_1));
  } catch(...) {
    __Pyx_CppExn2PyErr();
    __PYX_ERR(0, 8, __pyx_L1_error)
  }
  cpp_function(std::move(*__pyx_t_2));
  /* ... some more stuff ... */
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions