Skip to content
Josh Blum edited this page May 22, 2013 · 12 revisions
http://i.imgur.com/llH5R.png

PMC is a simple polymorphic container class for C++. A brief list of features:

  • Any C++ object can be contained in a PMC.
  • Reference counted and garbage collected.
  • Object interning supported on any type.
  • Const-correctness with the PMCC type.
  • Type indifferent equality comparable.
  • Serialization with boost serialize.
  • Python bindings for native conversions.

See the building wiki page for requirements and instructions: https://github.com/guruofquality/PMC/wiki/Building


Dive right in with some brief code snippets:

  • PMC_M makes a new PMC object
  • obj.is<type>() checks is the type
  • obj.as<type>() casts to the type
  • obj.eq(other) equality compares
#include <PMC/PMC.hpp>

//make a container with an empty FooBar
PMC p = PMC_M(FooBar());

//get a reference to foo bar
FooBar &fb = p.as<FooBar>();
fb.foo = 12345; //modify object

//read-only conatiner holding object
PMCC p_const = p;

//get a const-reference to foo bar
const FooBar &fb = p_const.as<FooBar>();
std::cout << fb.foo << std::endl; //read object

//create another FooBar, init first
FooBar fb2;
fb2.foo = 12345;
PMC p2 = PMC_M(fb2); //p2 now has a copy of fb2

//compare two PMCs:
std::cout << "should be true: " << p.eq(p2) << std::endl;

Object interning ensures that there is only one unique memory allocation for each unique object that is interned. https://en.wikipedia.org/wiki/String_interning

Interning a PMC has a high overhead cost due to lookup; the advantage is that comparison of interned objects has the equivalent overhead of comparing pointers.

//x0 and x1 are two different objects
PMCC x0 = PMC_M(int(42));
PMCC x1 = PMC_M(int(42));

//they are equality comparable but not equal
assert(x0.eq(x1));
assert(x0 != x1);

//now x0 and x1 are the same object
x0 = x0.intern();
x1 = x1.intern();

//they are equality comparable and equal
assert(x0.eq(x1));
assert(x0 == x1);

PMC objects can be easily serialized into strings and back. This is used to store PMC objects to file or send them over networks. The string representations can be in a non-portable but compact binary format ("BINARY"), or a portable, less compact ASCII text format ("TEXT"):

//------------------------------------------------
//-- example in c++
//------------------------------------------------

#include <PMC/PMC.hpp>

//create an arbitry PMC object
PMC p0 = PMC_M(42);

//serialize it into a string using "TEXT" format
std::string data = PMC::serialize(p0, "TEXT");

//deserialize the string back into a PMC object
PMCC p1 = PMC::deserialize(data, "TEXT");

#------------------------------------------------
#-- example in python
#------------------------------------------------

from PMC import *

#create an arbitry PMC object
p0 = PMC_M(42)

#serialize it into a string using "TEXT" format
data = PMC.serialize(p0, "TEXT")

#deserialize the string back into a PMC object
p1 = PMC.deserialize(data, "TEXT")

Advanced Usage: Any of the boost polymorphic archive classes can be used (binary, text, xml, others). The following code is an example that serializes and arbitrary PMCC object, and then de-serializes it from the intermediate stringstream form:

#include <boost/archive/polymorphic_text_iarchive.hpp>
#include <boost/archive/polymorphic_text_oarchive.hpp>
#include <sstream>

PMCC serialize_loopback_test(PMCC my_pmc_object_to_serialize)
{
    std::stringstream ss;
    boost::archive::polymorphic_text_oarchive oa(ss);

    oa << my_pmc_object_to_serialize;
    std::cout << "stringstream holds " << ss.str() << std::endl;

    boost::archive::polymorphic_text_iarchive ia(ss);
    PMCC my_deserialized_pmc_object;
    ia >> my_deserialized_pmc_object;

    return my_deserialized_pmc_object;
}

A PMC holding any type can be serialized, as long as the user registers the serialization. Build the following into a cpp file in your library:

#include <PMC/Serialize.hpp>

namespace boost { namespace serialization {
template <class Archive>
void serialize(Archive &ar, FooBar &t, const unsigned int)
{
    ar & t._foo;
    ar & t._bar;
}
}}

PMC_SERIALIZE_EXPORT(FooBar, "PMC<FooBar>")

http://i.imgur.com/o5c6Z.png

The PMC type can be converted into native python types and vice-versa. The PMC python module comes with a extendible type registry system. This registry is pre-loaded for most if not all built-in python data types. In addition, new types can easily be added to extend the type registry. See tests/custom_type_test.py as an example of registering a custom type.

#import PMC support, this brings in PMC support
from PMC import *

#all you need to know:
#convert an object to a PMC
my_pmc = PMC_M(some_python_object)

#convert a PMC into a native python object
#conversion uses the __call__ operator
my_python_object = my_pmc()

Need an example of registering a custom type into python? See the foo bar example in the tests directory:

This is what a typical registry looks like. Its basically 4 lines in your swig .i file if you remove comments and whitespace:

%include <PMC/Registry.i> //include the registration macros

//
// Export swig defintions for converting this object:
// - FooBar is the C++ type as defined in your header files
// - swig_foo_bar is the name of the swig functions DECL_ creates
//
DECL_PMC_SWIG_TYPE(FooBar, swig_foo_bar)

//
// import the python module
// This import statement is needed before the registration step
// The import statement is how it would look in your client code
//
%pythoncode %{
from foo_bar import *
%}

//
// Register the swigged type into PMC:
// - swig_foo_bar is the name of the swig functions made by DECL_
// - FooBar is the name of the python class for your object
//
REG_PMC_SWIG_TYPE(swig_foo_bar, FooBar)

Built-in python types do not support all variations of fixed-width numbers. Basically python only supports int, long, double, and complex double. Fortunately, numpy gives python support for all fixed-width numeric types. When numpy is available, the PMC module supports the following:

  • conversions for fixed width integer types (8, 16, 32, int/uint)
  • conversions for floating point types (32 and 64)
  • conversions for floating point complex types (64 and 128)

In addition, all std::vector of these numeric types convert to numpy arrays. The numpy arrays will actually reference the memory held in the std::vector.

To support built-in python container types: set, tuple, list, dict. These types have typedef'd C++ equivalents in <PMC/Containers.hpp>. The PMC module comes with conversions for all these container types. These types are recommended for C++ users interfacing with python.

Tuple Note: The C++ representation for the python tuple requires that a type be exported for every possible tuple length. However, it is not practical to export types from 0 to infinity. Therefore, only tuples up to lengths of 11 elements are supported. If this is an issue, its recommended to use the list type instead.