Skip to content

Commit 7849960

Browse files
committed
Adds C++ simplifying class for default arguments.
1 parent 6c00eb9 commit 7849960

File tree

1 file changed

+91
-0
lines changed

1 file changed

+91
-0
lines changed

doc/sphinx/source/parsing_arguments.rst

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,3 +581,94 @@ Here is that function implemented in C:
581581
Py_DECREF(must_log);
582582
return ret;
583583
}
584+
585+
^^^^^^^^^^^^^^^^^^^^^^^^
586+
Simplifying C++11 class
587+
^^^^^^^^^^^^^^^^^^^^^^^^
588+
589+
With C++ we can make this a bit smoother. We declare a class thus:
590+
591+
.. code-block:: cpp
592+
593+
/** Class to simplify default arguments.
594+
*
595+
* Usage:
596+
*
597+
* static DefaultArg arg_0(PyLong_FromLong(1L));
598+
* static DefaultArg arg_1(PyUnicode_FromString("Default string."));
599+
* if (! arg_0 || ! arg_1) {
600+
* return NULL;
601+
* }
602+
*
603+
* if (! PyArg_ParseTupleAndKeywords(args, kwargs, "...",
604+
const_cast<char**>(kwlist),
605+
&arg_0, &arg_1, ...)) {
606+
return NULL;
607+
}
608+
*
609+
* Then just use arg_0, arg_1 as if they were a PyObject* (possibly
610+
* might need to be cast to some specific PyObject*).
611+
*
612+
* WARN: This class is designed to be statically allocated. If allocated
613+
* on the heap or stack it will leak memory. That could be fixed by
614+
* implementing:
615+
*
616+
* ~DefaultArg() { Py_XDECREF(m_default); }
617+
*
618+
* But this will be highly dangerous when statically allocated as the
619+
* destructor will be invoked with the Python interpreter in an
620+
* uncertain state and will, most likely, segfault:
621+
* "Python(39158,0x7fff78b66310) malloc: *** error for object 0x100511300: pointer being freed was not allocated"
622+
*/
623+
class DefaultArg {
624+
public:
625+
DefaultArg(PyObject *new_ref) : m_arg { NULL }, m_default { new_ref } {}
626+
// Allow setting of the (optional) argument with
627+
// PyArg_ParseTupleAndKeywords
628+
PyObject **operator&() { m_arg = NULL; return &m_arg; }
629+
// Access the argument or the default if default.
630+
operator PyObject*() const {
631+
return m_arg ? m_arg : m_default;
632+
}
633+
// Test if constructed successfully from the new reference.
634+
explicit operator bool() { return m_default != NULL; }
635+
protected:
636+
PyObject *m_arg;
637+
PyObject *m_default;
638+
};
639+
640+
641+
And we can use ``DefaultArg`` like this:
642+
643+
.. code-block:: c
644+
645+
static PyObject*
646+
do_something(something *self, PyObject *args, PyObject *kwds) {
647+
PyObject *ret = NULL;
648+
/* Initialise default arguments. */
649+
static DefaultArg encoding { PyUnicode_FromString("utf-8") };
650+
static DefaultArg the_id { PyLong_FromLong(0L) };
651+
static DefaultArg must_log { PyBool_FromLong(1L) };
652+
653+
/* Check that the defaults are non-NULL i.e. succesful. */
654+
if (!encoding || !the_id || !must_log) {
655+
return NULL;
656+
}
657+
658+
static const char *kwlist[] = { "encoding", "the_id", "must_log", NULL };
659+
/* &encoding etc. accesses &m_arg in DefaultArg because of PyObject **operator&() */
660+
if (! PyArg_ParseTupleAndKeywords(args, kwds, "|Oip",
661+
const_cast<char**>(kwlist),
662+
&encoding, &the_id, &must_log)) {
663+
return NULL;
664+
}
665+
/*
666+
* Use encoding, the_id, must_log from here on as PyObject* since we have
667+
* operator PyObject*() const ...
668+
*
669+
* So if we have a function:
670+
* set_encoding(PyObject *obj) { ... }
671+
*/
672+
set_encoding(encoding);
673+
/* ... */
674+
}

0 commit comments

Comments
 (0)