-
Notifications
You must be signed in to change notification settings - Fork 2
Home
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.
Table of Contents
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>")
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:
- https://github.com/guruofquality/PMC/blob/master/tests/custom_type_test.py
- https://github.com/guruofquality/PMC/blob/master/tests/foo_bar.i
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.