archive works for pointers and shared_ptrs (even with

mult. inheritance and virtual base classes)
This commit is contained in:
Christopher Lackner 2018-12-03 16:28:04 +01:00
parent 7bfc48e8f3
commit 8e29d38fc1
12 changed files with 414 additions and 30 deletions

View File

@ -17,6 +17,7 @@ option( INTEL_MIC "cross compile for intel xeon phi")
option( INSTALL_PROFILES "install environment variable settings to /etc/profile.d" OFF )
option( USE_CCACHE "use ccache")
option( USE_INTERNAL_TCL "Compile tcl files into the code and don't install them" ON)
option( ENABLE_UNIT_TESTS "Enable Catch unit tests")
option( USE_SUPERBUILD "use ccache" ON)
@ -341,6 +342,11 @@ execute_process(COMMAND hdiutil create -volname Netgen -srcfolder ${CMAKE_INSTAL
enable_testing()
include(CTest)
if(ENABLE_UNIT_TESTS)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/external_projects/catch.cmake)
endif(ENABLE_UNIT_TESTS)
#######################################################################
add_subdirectory(libsrc)

View File

@ -140,6 +140,7 @@ set_vars( NETGEN_CMAKE_ARGS
INTEL_MIC
CMAKE_PREFIX_PATH
CMAKE_INSTALL_PREFIX
ENABLE_UNIT_TESTS
)
# propagate all variables set on the command line using cmake -DFOO=BAR

View File

@ -0,0 +1,18 @@
include (ExternalProject)
find_program(GIT_EXECUTABLE git)
ExternalProject_Add(
project_catch
PREFIX ${CMAKE_BINARY_DIR}/catch
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v2.0.1
TIMEOUT 10
UPDATE_COMMAND "" # ${GIT_EXECUTABLE} pull
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD ON
)
# Expose required variable (CATCH_INCLUDE_DIR) to parent scope
ExternalProject_Get_Property(project_catch source_dir)
set(CATCH_INCLUDE_DIR ${source_dir}/single_include CACHE INTERNAL "Path to include folder for Catch")

View File

@ -1,8 +1,10 @@
add_definitions(-DNGINTERFACE_EXPORTS)
add_library(ngcore OBJECT basearchive.cpp)
add_library(ngcore basearchive.cpp)
set_target_properties(ngcore PROPERTIES POSITION_INDEPENDENT_CODE ON )
install(TARGETS ngcore DESTINATION ${NG_INSTALL_DIR} COMPONENT netgen)
install(FILES ngcore.hpp archive.hpp basearchive.hpp
DESTINATION ${NG_INSTALL_DIR_INCLUDE}/core COMPONENT netgen_devel
)

View File

@ -16,6 +16,7 @@ namespace ngcore
: BinaryOutArchive(std::make_shared<std::ofstream>(filename)) {}
virtual ~BinaryOutArchive () { FlushBuffer(); }
using Archive::operator&;
virtual Archive & operator & (double & d)
{ return Write(d); }
virtual Archive & operator & (int & i)
@ -81,6 +82,7 @@ namespace ngcore
BinaryInArchive (std::string filename)
: BinaryInArchive(std::make_shared<std::ifstream>(filename)) {}
using Archive::operator&;
virtual Archive & operator & (double & d)
{ Read(d); return *this; }
virtual Archive & operator & (int & i)

View File

@ -3,9 +3,9 @@
namespace ngcore
{
std::map<std::string, std::function<void*()>>& GetArchiveRegister()
std::map<std::string, ClassArchiveInfo>& GetArchiveRegister()
{
static std::map<std::string, std::function<void*()>> type_register = {};
static std::map<std::string, ClassArchiveInfo> type_register;
return type_register;
}
}

View File

@ -20,7 +20,31 @@ namespace ngcore
static constexpr bool value = type::value;
};
std::map<std::string, std::function<void*()>>& GetArchiveRegister();
// Info stored by registering a class using the RegisterClassForArchive struct in the map
// stored in GetArchiveRegister
struct ClassArchiveInfo
{
// create new object of this type and return a void* pointer that is points to the location
// of the (base)class given by type_info
std::function<void*(const std::type_info&)> creator;
// This caster takes a void* pointer to the type stored in this info and casts it to a
// void* pointer pointing to the (base)class type_info
std::function<void*(const std::type_info&, void*)> upcaster;
// This caster takes a void* pointer to the (base)class type_info and returns void* pointing
// to the type stored in this info
std::function<void*(const std::type_info&, void*)> downcaster;
};
// Returns a map of from the mangled typeids to the ClassArchiveInfo
std::map<std::string, ClassArchiveInfo>& GetArchiveRegister();
// Helper class for up-/downcasting
template<typename T, typename ... Bases>
struct Caster
{
static void* tryUpcast(const std::type_info& ti, T* p);
static void* tryDowncast(const std::type_info& ti, void* p);
};
// Base Archive class
class Archive
@ -76,11 +100,12 @@ namespace ngcore
size = v.size();
(*this) & size;
if(!is_output)
v.reserve(size);
v.resize(size);
Do(&v[0], size);
return (*this);
}
// Archive arrays =====================================================
// this functions can be overloaded in Archive implementations for more efficiency
template <typename T>
Archive & Do (T * data, size_t n)
{ for (size_t j = 0; j < n; j++) { (*this) & data[j]; }; return *this; };
@ -122,32 +147,87 @@ namespace ngcore
// save -2 for nullptr
if(!ptr)
return (*this) << -2;
auto pos = shared_ptr2nr.find((void*) ptr.get());
void* reg_ptr = ptr.get();
bool neededDowncast = false;
if constexpr(has_DoArchive<T>::value)
{
if(GetArchiveRegister().count(std::string(typeid(*ptr).name())) == 0)
throw std::runtime_error(std::string("Archive error: Polymorphic type ")
+ typeid(*ptr).name()
+ " not registered for archive");
else
reg_ptr = GetArchiveRegister()[typeid(*ptr).name()].downcaster(typeid(T), ptr.get());
if(reg_ptr != (void*) ptr.get())
neededDowncast = true;
}
auto pos = shared_ptr2nr.find(reg_ptr);
// if not found store -1 and the pointer
if(pos == shared_ptr2nr.end())
{
shared_ptr2nr[(void*) ptr.get()] = shared_ptr_count++;
auto p = ptr.get();
return (*this) << -1 & p;
(*this) << -1;
(*this) & neededDowncast & p;
if(neededDowncast)
(*this) << std::string(typeid(*ptr).name());
shared_ptr2nr[reg_ptr] = shared_ptr_count++;
return *this;
}
// if found store the position
return (*this) << pos->second;
// if found store the position and if it has to be downcasted and how
(*this) << pos->second << neededDowncast;
if(neededDowncast)
(*this) << std::string(typeid(*ptr).name());
return (*this);
}
else // Input
{
int nr;
(*this) & nr;
if(nr == -2)
{
ptr = nullptr;
return *this;
}
else if (nr == -1)
{
T* p;
(*this) & p;
bool neededDowncast;
(*this) & neededDowncast & p;
ptr = std::shared_ptr<T>(p);
if(neededDowncast)
{
std::string name;
(*this) & name;
auto info = GetArchiveRegister()[name];
nr2shared_ptr.push_back(std::shared_ptr<void>(std::static_pointer_cast<void>(ptr),
info.downcaster(typeid(T),
ptr.get())));
}
else
nr2shared_ptr.push_back(ptr);
}
else
ptr = std::reinterpret_pointer_cast<T>(nr2shared_ptr[nr]);
{
auto other = nr2shared_ptr[nr];
bool neededDowncast;
(*this) & neededDowncast;
if(neededDowncast)
{
if constexpr(has_DoArchive<T>::value)
{
std::string name;
(*this) & name;
auto info = GetArchiveRegister()[name];
ptr = std::static_pointer_cast<T>(std::shared_ptr<void>(other,
info.upcaster(typeid(T),
other.get())));
}
else
throw std::runtime_error("Shouldn't get here...");
}
else
ptr = std::static_pointer_cast<T>(other);
}
}
return *this;
}
@ -165,11 +245,21 @@ namespace ngcore
(*this) & m2;
return *this;
}
auto pos = ptr2nr.find( (void*) p);
void* reg_ptr = (void*)p;
if constexpr(has_DoArchive<T>::value)
{
if(GetArchiveRegister().count(std::string(typeid(*p).name())) == 0)
throw std::runtime_error(std::string("Archive error: Polimorphic type ")
+ typeid(*p).name()
+ " not registered for archive");
else
reg_ptr = GetArchiveRegister()[typeid(*p).name()].downcaster(typeid(T), p);
}
auto pos = ptr2nr.find(reg_ptr);
// if the pointer is not found in the map create a new entry
if (pos == ptr2nr.end())
{
ptr2nr[(void*) p] = ptr_count++;
ptr2nr[reg_ptr] = ptr_count++;
if(typeid(*p) == typeid(T))
if constexpr (std::is_constructible<T>::value)
{
@ -183,7 +273,7 @@ namespace ngcore
// We want this special behaviour only for our classes that implement DoArchive
if constexpr(has_DoArchive<T>::value)
{
if(GetArchiveRegister().count(typeid(*p).name()) == 0)
if(GetArchiveRegister().count(std::string(typeid(*p).name())) == 0)
throw std::runtime_error(std::string("Archive error: Polimorphic type ")
+ typeid(*p).name()
+ " not registered for archive");
@ -199,13 +289,13 @@ namespace ngcore
else
{
(*this) & pos->second;
(*this) << std::string(typeid(*p).name());
}
}
else
{
int nr;
(*this) & nr;
// cout << "in, got nr " << nr << endl;
if (nr == -2)
{
p = nullptr;
@ -215,9 +305,8 @@ namespace ngcore
if constexpr (std::is_constructible<T>::value)
{
p = new T;
// cout << "create new ptr, p = " << p << endl;
(*this) & *p;
nr2ptr.push_back(p);
(*this) & *p;
}
else
throw std::runtime_error("Class isn't registered properly");
@ -230,16 +319,25 @@ namespace ngcore
{
std::string name;
(*this) & name;
p = reinterpret_cast<T*>(GetArchiveRegister()[name]());
nr2ptr.push_back(p);
auto info = GetArchiveRegister()[name];
p = (T*) info.creator(typeid(T));
nr2ptr.push_back(info.downcaster(typeid(T),p));
(*this) & *p;
}
else
throw std::runtime_error("Class isn't registered properly");
}
else
{
p = (T*)nr2ptr[nr];
// cout << "reuse ptr " << nr << ": " << p << endl;
std::string name;
(*this) & name;
if constexpr(has_DoArchive<T>::value)
{
auto info = GetArchiveRegister()[name];
p = (T*) info.upcaster(typeid(T), nr2ptr[nr]);
}
else
p = (T*) nr2ptr[nr];
}
}
return *this;
@ -253,14 +351,62 @@ namespace ngcore
(*this) & ht;
return *this;
}
virtual void FlushBuffer() {}
};
template<typename T, typename ... Bases>
class RegisterClassForArchive
{
public:
RegisterClassForArchive()
{
static_assert(std::is_constructible_v<T>, "Class registered for archive must be default constructible");
ClassArchiveInfo info;
info.creator = [this,&info](const std::type_info& ti) -> void*
{ return typeid(T) == ti ? new T : Caster<T, Bases...>::tryUpcast(ti, new T); };
info.upcaster = [this](const std::type_info& ti, void* p) -> void*
{ return typeid(T) == ti ? p : Caster<T, Bases...>::tryUpcast(ti, (T*) p); };
info.downcaster = [this](const std::type_info& ti, void* p) -> void*
{ return typeid(T) == ti ? p : Caster<T, Bases...>::tryDowncast(ti, p); };
GetArchiveRegister()[std::string(typeid(T).name())] = info;
}
};
template<typename T>
void RegisterClassForArchive()
struct Caster<T>
{
static_assert(std::is_constructible_v<T>, "Class registered for archive must be default constructible");
GetArchiveRegister()[std::string(typeid(T).name())] = []() -> void* { return new T; };
static void* tryUpcast (const std::type_info& ti, T* p)
{
throw std::runtime_error("Upcast not successful, some classes are not registered properly for archiving!");
}
static void* tryDowncast (const std::type_info& ti, void* p)
{
throw std::runtime_error("Downcast not successful, some classes are not registered properly for archiving!");
}
};
template<typename T, typename B1, typename ... Brest>
struct Caster<T,B1,Brest...>
{
static void* tryUpcast(const std::type_info& ti, T* p)
{
try
{ return GetArchiveRegister()[typeid(B1).name()].upcaster(ti, (void*) (dynamic_cast<B1*>(p))); }
catch(std::exception)
{ return Caster<T, Brest...>::tryUpcast(ti, p); }
}
static void* tryDowncast(const std::type_info& ti, void* p)
{
if(typeid(B1) == ti)
return dynamic_cast<T*>((B1*) p);
try
{ return GetArchiveRegister()[typeid(B1).name()].downcaster(ti, (void*) ((B1*)p)); }
catch(std::exception)
{ return Caster<T, Brest...>::tryDowncast(ti, p); }
}
};
}
#endif // NG_BASEARCHIVE_HPP

View File

@ -4,7 +4,6 @@ if(NOT WIN32)
$<TARGET_OBJECTS:la>
$<TARGET_OBJECTS:gprim>
$<TARGET_OBJECTS:gen>
$<TARGET_OBJECTS:ngcore>
)
endif(NOT WIN32)
@ -31,7 +30,7 @@ if(APPLE)
endif(APPLE)
if(NOT WIN32)
target_link_libraries( mesh ${ZLIB_LIBRARIES} ${MPI_CXX_LIBRARIES} ${PYTHON_LIBRARIES} ${METIS_LIBRARY})
target_link_libraries( mesh ngcore ${ZLIB_LIBRARIES} ${MPI_CXX_LIBRARIES} ${PYTHON_LIBRARIES} ${METIS_LIBRARY})
install( TARGETS mesh ${NG_INSTALL_DIR})
endif(NOT WIN32)

View File

@ -1 +1,2 @@
add_subdirectory(catch)
add_subdirectory(pytest)

View File

@ -0,0 +1,30 @@
if(ENABLE_UNIT_TESTS)
add_custom_target(unit_tests)
# Build catch_main test object
message("netgen include dir = ${NETGEN_INCLUDE_DIR_ABSOLUTE} --------------------------------------")
include_directories(${CATCH_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../libsrc/include})
add_library(catch_main STATIC main.cpp)
set_target_properties(catch_main PROPERTIES CXX_STANDARD 17)
add_dependencies(unit_tests catch_main)
add_dependencies(catch_main project_catch)
# ensure the test targets are built before testing
add_test(NAME unit_tests_built COMMAND ${CMAKE_COMMAND} --build . --target unit_tests --config ${CMAKE_BUILD_TYPE} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../.. )
macro(add_unit_test name sources)
add_executable(test_${name} ${sources} )
if (WIN32)
target_link_libraries(test_${name} ngcore catch_main)
else(WIN32)
target_link_libraries(test_${name} ngcore catch_main)
endif(WIN32)
add_dependencies(unit_tests test_${name})
add_test(NAME unit_${name} COMMAND test_${name})
set_tests_properties(unit_${name} PROPERTIES DEPENDS unit_tests_built)
endmacro()
add_unit_test(archive archive.cpp)
endif(ENABLE_UNIT_TESTS)

176
tests/catch/archive.cpp Normal file
View File

@ -0,0 +1,176 @@
#include "catch.hpp"
#include <../core/ngcore.hpp>
using namespace ngcore;
using namespace std;
class CommonBase
{
public:
virtual ~CommonBase() {}
virtual void DoArchive(Archive& archive) { }
};
class SharedPtrHolder : virtual public CommonBase
{
public:
vector<shared_ptr<string>> names;
virtual ~SharedPtrHolder()
{ }
virtual void DoArchive(Archive& archive) { archive & names; }
};
class PtrHolder : virtual public CommonBase
{
public:
vector<int*> numbers;
virtual ~PtrHolder() {}
virtual void DoArchive(Archive& archive) { archive & numbers; }
};
class SharedPtrAndPtrHolder : public SharedPtrHolder, public PtrHolder
{
public:
virtual ~SharedPtrAndPtrHolder() {}
virtual void DoArchive(Archive& archive)
{
SharedPtrHolder::DoArchive(archive);
PtrHolder::DoArchive(archive);
}
};
class NotRegisteredForArchive : public SharedPtrAndPtrHolder {};
class OneMoreDerivedClass : public SharedPtrAndPtrHolder {};
static RegisterClassForArchive<CommonBase> regb;
static RegisterClassForArchive<SharedPtrHolder, CommonBase> regsp;
static RegisterClassForArchive<PtrHolder, CommonBase> regp;
static RegisterClassForArchive<SharedPtrAndPtrHolder, SharedPtrHolder, PtrHolder> regspp;
static RegisterClassForArchive<OneMoreDerivedClass, SharedPtrAndPtrHolder> regom;
void testSharedPointer(Archive& in, Archive& out)
{
SECTION("Same shared ptr")
{
static_assert(has_DoArchive<SharedPtrHolder>::value, "");
SharedPtrHolder holder, holder2;
holder.names.push_back(make_shared<string>("name"));
holder2.names = holder.names; // same shared ptr
out & holder & holder2;
out.FlushBuffer();
SharedPtrHolder inholder, inholder2;
in & inholder & inholder2;
CHECK(inholder.names.size() == 1);
CHECK(inholder.names[0] == inholder2.names[0]);
CHECK(inholder.names[0].use_count() == 3); // one shared ptr is still kept in the archive
CHECK(*inholder.names[0] == "name");
}
}
void testPointer(Archive& in, Archive& out)
{
SECTION("Same pointer")
{
PtrHolder holder, holder2;
holder.numbers.push_back(new int(3));
holder2.numbers = holder.numbers; // same shared ptr
out & holder & holder2;
out.FlushBuffer();
PtrHolder inholder, inholder2;
in & inholder & inholder2;
CHECK(inholder.numbers.size() == 1);
CHECK(inholder.numbers[0] == inholder2.numbers[0]);
CHECK(*inholder.numbers[0] == 3);
}
}
void testMultipleInheritance(Archive& in, Archive& out)
{
PtrHolder* p = new OneMoreDerivedClass;
p->numbers.push_back(new int(2));
auto p2 = dynamic_cast<SharedPtrHolder*>(p);
p2->names.push_back(make_shared<string>("test"));
auto sp1 = shared_ptr<PtrHolder>(p);
auto sp2 = dynamic_pointer_cast<SharedPtrHolder>(sp1);
auto checkPtr = [] (auto pin, auto pin2)
{
CHECK(typeid(*pin) == typeid(*pin2));
CHECK(typeid(*pin) == typeid(OneMoreDerivedClass));
CHECK(*pin2->names[0] == "test");
CHECK(*pin->numbers[0] == 2);
CHECK(dynamic_cast<SharedPtrAndPtrHolder*>(pin) == dynamic_cast<SharedPtrAndPtrHolder*>(pin2));
REQUIRE(dynamic_cast<SharedPtrAndPtrHolder*>(pin2) != nullptr);
CHECK(*dynamic_cast<SharedPtrAndPtrHolder*>(pin2)->numbers[0] == 2);
CHECK(*pin->numbers[0] == *dynamic_cast<SharedPtrAndPtrHolder*>(pin2)->numbers[0]);
REQUIRE(dynamic_cast<SharedPtrAndPtrHolder*>(pin) != nullptr);
CHECK(dynamic_cast<SharedPtrAndPtrHolder*>(pin)->names[0] == pin2->names[0]);
};
SECTION("Archive ptrs to leaves of mult. inh.")
{
out & p & p2;
out.FlushBuffer();
PtrHolder* pin;
SharedPtrHolder* pin2;
in & pin & pin2;
checkPtr(pin, pin2);
}
SECTION("Archive shared ptrs to leaves of mult. inh.")
{
out & sp1 & sp2;
out.FlushBuffer();
shared_ptr<PtrHolder> pin;
shared_ptr<SharedPtrHolder> pin2;
in & pin & pin2;
checkPtr(pin.get(), pin2.get());
}
SECTION("Virtual base class")
{
CommonBase* b = dynamic_cast<CommonBase*>(p);
out & b & p;
PtrHolder* pin;
CommonBase* bin;
in & bin & pin;
checkPtr(pin, dynamic_cast<SharedPtrHolder*>(bin));
}
}
void testArchive(Archive& in, Archive& out)
{
SECTION("SharedPtr")
{
testSharedPointer(in, out);
}
SECTION("Pointer")
{
testPointer(in, out);
}
SECTION("Multiple inheritance")
{
testMultipleInheritance(in, out);
}
SECTION("Not registered")
{
SharedPtrAndPtrHolder* p = new NotRegisteredForArchive;
REQUIRE_THROWS(out & p, Catch::Contains("not registered for archive"));
}
}
TEST_CASE("BinaryArchive")
{
auto stream = make_shared<stringstream>();
BinaryOutArchive out(stream);
BinaryInArchive in(stream);
testArchive(in, out);
}
TEST_CASE("TextArchive")
{
auto stream = make_shared<stringstream>();
TextOutArchive out(stream);
TextInArchive in(stream);
testArchive(in, out);
}

3
tests/catch/main.cpp Normal file
View File

@ -0,0 +1,3 @@
#define CATCH_CONFIG_MAIN
#include <catch.hpp>