From de50bf7c917d8a31e506bb84e8f56ee13ffd7e44 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 20 Apr 2015 03:41:28 +0200 Subject: [PATCH] virtual copy support documented and covered with unit test --- src/lib/meta/virtual-copy-support.hpp | 777 ++++++------------ src/lib/variant.hpp | 26 +- tests/12metaprogramming.tests | 4 + tests/library/meta/generator-test.cpp | 3 +- .../meta/virtual-copy-support-test.cpp | 269 +++++- 5 files changed, 532 insertions(+), 547 deletions(-) diff --git a/src/lib/meta/virtual-copy-support.hpp b/src/lib/meta/virtual-copy-support.hpp index f8a60b618..bdfd070b8 100644 --- a/src/lib/meta/virtual-copy-support.hpp +++ b/src/lib/meta/virtual-copy-support.hpp @@ -22,45 +22,88 @@ /** @file virtual-copy-support.hpp - ** A typesafe union record to carry embedded values of unrelated type. - ** This file defines a simple alternative to boost::variant. It pulls in - ** fewer headers, has a shorter code path and is hopefully more readable, - ** but also doesn't deal with alignment issues and is not threadsafe. - ** - ** Deliberately, the design rules out re-binding of the contained type. Thus, - ** once created, a variant \em must hold a valid element and always an element - ** of the same type. Beyond that, variant elements are copyable and mutable. - ** Direct access requires knowledge of the embedded type (no switch-on type). - ** Type mismatch is checked at runtime. As a fallback, we provide a visitor - ** scheme for generic access. + ** Helper for building "virtual copy" operations. + ** Especially in conjunction with type erasure, sometimes we have to deal with + ** situations, where we want to copy or assign an object without knowing the + ** full implementation type. Obviously, the default (compiler generated) operations + ** will not work in such situations -- even worse, they will slice the objects on + ** copy. The reason is: we \em must know the full implementation and storage layout + ** of a type do provide any meaningful copy, assignment or move operation. ** - ** The design restrictions were chosen deliberately, since a variant type might - ** promote "probe and switch on type" style programming, which is known to be fragile. - ** Likewise, we do not want to support mutations of the variant type at runtime. Basically, - ** using a variant record is recommended only if either the receiving context has structural - ** knowledge about the type to expect, or when a visitor implementation can supply a sensible - ** handling for \em all the possible types. As an alternative, you might consider the - ** lib::PolymorphicValue to hold types implementing a common interface. + ** A possible workaround is to call into the actual implementation type through + ** a virtual function (which requires a VTable): Even if for everyone else any + ** knowledge regarding the exact implementation type has been discarded ("erased"), + ** the function pointers in the VTable still implicitly hold onto that precise + ** implementation type, since they were setup during construction, where the + ** type was still available. Such a scheme of dealing with "opaque" copy operations + ** is known as virtual copy -- it can be dangerous and tricky to get right + ** and is preferably used only in flat class hierarchies. ** - ** \par implementation notes - ** We use a similar "double capsule" implementation technique as for lib::OpaqueHolder. - ** In fact, Variant is almost identical to the latter, just omitting unnecessary flexibility. - ** The outer capsule exposes the public handling interface, while the inner, private capsule - ** is a polymorphic value holder. Since C++ as such does not support polymorphic values, - ** the inner capsule is placed "piggyback" into a char buffer. The actual value is carried - ** within yet another, nested char buffer. Thus, effectively the first "slot" of the storage - ** will hold the VTable pointer, thereby encoding the actual type information -- leading to - ** a storage requirement of MAX plus one "slot" for the VTable. (with "slot" we - ** denote the smallest disposable storage size for the given platform after alignment, - ** typically the size of a size_t). + ** This helper template simplifies the construction of such a scheme. + ** - a base interface defines the available virtual copy operations + ** - a set of CRTP-style templates covers all the case of + ** - full copy support + ** - copy construction but not assignment + ** - only move construction allowed + ** - noncopyable type + ** - we use type traits and a policy template to pick the correct implementation + ** for a given data type. Any assignment or copy operations not supported by the + ** target type will be replaced by an implementation which raises a runtime error + ** (Exception). ** - ** To support copying and assignment of variant instances, but limit these operations - ** to variants holding the same type, we use a virtual assignment function. In case the - ** concrete type does not support assignment or copy construction, the respective access - ** function is replaced by an implementation raising a runtime error. + ** \par prerequisites + ** The actual implementation type needs to provide the necessary standard / custom + ** copy and assignment operations with the usual signature. Moreover, the implementation + ** type provides a static function \c downcast(Base&) to perform a suitable dynamic + ** or static downcast from the interface type to the concrete implementation type. ** - ** @see Veriant_test - ** @see lib::diff::GenNode + ** \par usage + ** The provided virtual operations are to be used in "backward" direction: invoked + ** on the source and affecting the target. The source, through the VTable, knows its + ** precise type and data layout. The target is handed in as parameter and treated + ** by the \c downcast() function -- which preferably performs a dynamic cast + ** (or at least asserts the correct type). This whole scheme can only work for + ** copy and assignment of objects, which actually have the same implementation + ** type -- it will never be possible to "cross copy" to an completely unrelated + ** target type, at least not generically. + ** + ** Usage example + ** \code{.cpp} + ** Variant (Variant const& ref) + ** { + ** ref.buffer().copyInto (&storage_); + ** } + ** + ** Variant& + ** operator= (Variant const& ovar) + ** { + ** ovar.buffer().copyInto (this->buffer()); + ** return *this; + ** } + ** + ** Buffer const& + ** buffer() const + ** { + ** return *reinterpret_cast (&storage_); + ** } + ** \endcode + ** In this example, the concrete implementation of the \c Buffer interface + ** will mix in the Policy template with the implementations of the virtual copy + ** operations. The copy constructor uses the virtual \c copyInto(void*) to perform + ** a "placement new", whereas the assignment operator calls the virtual \c copyInto(Buffer&) + ** to downcast the target \c Buffer and in the end invokes the assignment operator on + ** the concrete \c Buffer implementation subclass. + ** + ** @warning please make sure that \em only the virtual copy operation is invoked, since + ** this operation will delegate to the copy operation on the implementation class + ** and thus already invoke the whole chain of custom / compiler generated copy + ** implementations. Ignoring this warning can lead to insidious slicing or partial + ** copies. Additionally, if you \em really need multiple level deep inheritance, + ** you need to mix in the copy implementations on \em every level \em again, and + ** you need to provide custom copy operations on every level. + ** + ** @see VirtualCopySupport_test + ** @see lib::Variant usage example ** */ @@ -69,541 +112,207 @@ #define LIB_META_VIRTUAL_COPY_SUPPORT_H -#include "lib/meta/typelist.hpp" -#include "lib/meta/typelist-util.hpp" -#include "lib/meta/generator.hpp" +#include "lib/error.hpp" #include - +#include namespace lib { - - using std::string; +namespace time{ + class Time; // forward declaration for GCC 4.7 workaround +}} + +namespace lib { +namespace meta{ using std::move; - using std::forward; using util::unConst; + using std::is_move_constructible; + using std::is_copy_constructible; + using std::is_copy_assignable; + using std::enable_if; + namespace error = lumiera::error; - namespace { // implementation helpers - - using std::is_constructible; - using std::is_move_constructible; - using std::is_copy_constructible; - using std::is_copy_assignable; - using std::remove_reference; - using std::enable_if; - using meta::NullType; - using meta::Node; - - - template - struct CanBuildFrom - : CanBuildFrom::type - ,typename TYPES::List - > - { }; - - template - struct CanBuildFrom> - { - using Type = X; - }; - - template - struct CanBuildFrom> - { - using Type = typename CanBuildFrom::Type; - }; - - template - struct CanBuildFrom - { - static_assert (0 > sizeof(X), "No type in Typelist can be built from the given argument"); - }; - - - - - using EmptyBase = struct{}; - - template - class VirtualCopySupportInterface - : public BASE - { - public: - virtual void copyInto (void* targetStorage) const =0; - virtual void moveInto (void* targetStorage) =0; - virtual void copyInto (IFA& target) const =0; - virtual void moveInto (IFA& target) =0; - }; - - - - template - class NoCopyMoveSupport - : public B - { - virtual void - copyInto (void*) const override - { - throw error::Logic("Copy construction invoked but target is noncopyable"); - } - - virtual void - moveInto (void*) override - { - throw error::Logic("Move construction invoked but target is noncopyable"); - } - - virtual void - copyInto (B&) const override - { - throw error::Logic("Assignment invoked but target is not assignable"); - } - - virtual void - moveInto (B&) override - { - throw error::Logic("Assignment invoked but target is not assignable"); - } - }; - - - template - class MoveSupport - : public NoCopyMoveSupport - { - virtual void - copyInto (void*) const override - { - throw error::Logic("Copy construction invoked but target allows only move construction"); - } - - virtual void - moveInto (void* targetStorage) override - { - D& src = static_cast (*this); - new(targetStorage) D(move(src)); - } - }; - - - template - class CloneSupport - : public MoveSupport - { - virtual void - copyInto (void* targetStorage) const override - { - D const& src = static_cast (*this); - new(targetStorage) D(src); - } - }; - - - template - class FullCopySupport - : public CloneSupport - { - virtual void - copyInto (B& target) const override - { - D& t = D::downcast(target); - D const& s = static_cast (*this); - t = s; - } - - virtual void - moveInto (B& target) override - { - D& t = D::downcast(target); - D& s = static_cast (*this); - t = move(s); - } - }; - - - - - /** workaround for GCC 4.7: need to exclude some types, - * since they raise private access violation during probing. - * Actually, in C++11 such a case should trigger substitution - * failure, not an compilation error */ - template - struct can_use_assignment - : is_copy_assignable - { }; - - template<> - struct can_use_assignment - { static constexpr bool value = false; }; - - - - template - struct use_if_supports_only_move - : enable_if< is_move_constructible::value - && !is_copy_constructible::value - && !can_use_assignment::value - > - { }; - - template - struct use_if_supports_cloning - : enable_if< is_move_constructible::value - && is_copy_constructible::value - && !can_use_assignment::value - > - { }; - - template - struct use_if_supports_copy_and_assignment - : enable_if< is_move_constructible::value - && is_copy_constructible::value - && can_use_assignment::value - > - { }; - - - - - - template - struct CopySupport - { - template - using Policy = NoCopyMoveSupport; - }; - - template - struct CopySupport::type> - { - template - using Policy = MoveSupport; - }; - - template - struct CopySupport::type> - { - template - using Policy = CloneSupport; - }; - - template - struct CopySupport::type> - { - template - using Policy = FullCopySupport; - }; - - - - template - struct ValueAcceptInterface - { - virtual void handle(VAL&) { /* NOP */ }; - }; - - template - using VisitorInterface - = meta::InstantiateForEach; - - - }//(End) implementation helpers + using EmptyBase = struct{}; - - /** - * Typesafe union record. - * A Variant element may carry an embedded value of any of the predefined types. - * The type may not be rebound: It must be created holding some value and each - * instance is fixed to the specific type used at construction time. - * Yet within the same type, variant elements are copyable and assignable. - * The embedded type is erased on the signature, but knowledge about the - * actual type is retained, encoded into the embedded VTable. Thus, - * any access to the variant's value requires knowledge of the type - * in question, but type mismatch will provoke an exception at runtime. - * Generic access is possible using a visitor. - * @warning not threadsafe - */ - template - class Variant + template + class VirtualCopySupportInterface + : public BASE { public: - enum { SIZ = meta::maxSize::value }; - - class Visitor - : public VisitorInterface + virtual void copyInto (void* targetStorage) const =0; + virtual void moveInto (void* targetStorage) =0; + virtual void copyInto (IFA& target) const =0; + virtual void moveInto (IFA& target) =0; + }; + + + + template + class NoCopyMoveSupport + : public B + { + virtual void + copyInto (void*) const override { - public: - virtual ~Visitor() { } ///< this is an interface - }; - - - private: - /** Inner capsule managing the contained object (interface) */ - struct Buffer - : VirtualCopySupportInterface - { - char content_[SIZ]; - - void* ptr() { return &content_; } - - - virtual ~Buffer() {} ///< this is an ABC with VTable - - virtual void dispatch (Visitor&) =0; - virtual operator string() const =0; - }; - - - /** concrete inner capsule specialised for a given type */ - template - struct Buff - : CopySupport::template Policy> - { - static_assert (SIZ >= sizeof(TY), "Variant record: insufficient embedded Buffer size"); - - TY& - access() const ///< core operation: target is contained within the inline buffer - { - return *reinterpret_cast (unConst(this)->ptr()); - } - - ~Buff() - { - access().~TY(); - } - - Buff (TY const& obj) - { - new(Buffer::ptr()) TY(obj); - } - - Buff (TY && robj) - { - new(Buffer::ptr()) TY(move(robj)); - } - - Buff (Buff const& oBuff) - { - new(Buffer::ptr()) TY(oBuff.access()); - } - - Buff (Buff && rBuff) - { - new(Buffer::ptr()) TY(move (rBuff.access())); - } - - void - operator= (Buff const& buff) - { - *this = buff.access(); - } - - void - operator= (Buff&& rref) - { - *this = move (rref.access()); - } - - void - operator= (TY const& ob) - { - if (&ob != Buffer::ptr()) - this->access() = ob; - } - - void - operator= (TY && rob) - { - this->access() = move(rob); - } - - - - static Buff& - downcast (Buffer& b) - { - Buff* buff = dynamic_cast (&b); - - if (!buff) - throw error::Logic("Variant type mismatch: " - "the given variant record does not hold " - "a value of the type requested here" - ,error::LUMIERA_ERROR_WRONG_TYPE); - else - return *buff; - } - - void - dispatch (Visitor& visitor) - { - ValueAcceptInterface& typeDispatcher = visitor; - typeDispatcher.handle (this->access()); - } - - /** diagnostic helper */ - operator string() const; - }; - - enum{ BUFFSIZE = sizeof(Buffer) }; - - /** embedded buffer actually holding the concrete Buff object, - * which in turn holds and manages the target object. - * @note Invariant: always contains a valid Buffer subclass */ - char storage_[BUFFSIZE]; - - - - - - protected: /* === internal interface for managing the storage === */ - - Buffer& - buffer() - { - return *reinterpret_cast (&storage_); - } - Buffer const& - buffer() const - { - return *reinterpret_cast (&storage_); + throw error::Logic("Copy construction invoked but target is noncopyable"); } - template - Buff& - buff() + virtual void + moveInto (void*) override { - return Buff::downcast(this->buffer()); + throw error::Logic("Move construction invoked but target is noncopyable"); } - - public: - ~Variant() + virtual void + copyInto (I&) const override { - buffer().~Buffer(); + throw error::Logic("Assignment invoked but target is not assignable"); } - Variant() + virtual void + moveInto (I&) override { - using DefaultType = typename TYPES::List::Head; - - new(storage_) Buff (DefaultType()); + throw error::Logic("Assignment invoked but target is not assignable"); + } + }; + + + template + class MoveSupport + : public NoCopyMoveSupport + { + virtual void + copyInto (void*) const override + { + throw error::Logic("Copy construction invoked but target allows only move construction"); } - template - Variant(X&& x) + virtual void + moveInto (void* targetStorage) override { - using StorageType = typename CanBuildFrom::Type; - - new(storage_) Buff (forward(x)); + D& src = static_cast (*this); + new(targetStorage) D(move(src)); + } + }; + + + template + class CloneSupport + : public MoveSupport + { + virtual void + copyInto (void* targetStorage) const override + { + D const& src = static_cast (*this); + new(targetStorage) D(src); + } + }; + + + template + class FullCopySupport + : public CloneSupport + { + virtual void + copyInto (I& target) const override + { + D& t = D::downcast(target); + D const& s = static_cast (*this); + t = s; } - Variant (Variant& ref) + virtual void + moveInto (I& target) override { - ref.buffer().copyInto (&storage_); - } - - Variant (Variant const& ref) - { - ref.buffer().copyInto (&storage_); - } - - Variant (Variant&& rref) - { - rref.buffer().moveInto (&storage_); - } - - template - Variant& - operator= (X x) - { - using RawType = typename remove_reference::type; - static_assert (meta::isInList(), - "Type error: the given variant could never hold the required type"); - static_assert (can_use_assignment::value, "target type does not support assignment"); - - buff() = forward(x); - return *this; - } - - Variant& - operator= (Variant& ovar) - { - ovar.buffer().copyInto (this->buffer()); - return *this; - } - - Variant& - operator= (Variant const& ovar) - { - ovar.buffer().copyInto (this->buffer()); - return *this; - } - - Variant& - operator= (Variant&& rvar) - { - rvar.buffer().moveInto (this->buffer()); - return *this; - } - - - /** diagnostic helper */ - operator string() const; - - - /* === Access === */ - - template - X& - get() - { - static_assert (meta::isInList(), - "Type error: the given variant could never hold the required type"); - - return buff().access(); - } - - - void - accept (Visitor& visitor) - { - buffer().dispatch (visitor); + D& t = D::downcast(target); + D& s = static_cast (*this); + t = move(s); } }; -} // namespace lib - - - - /* == diagnostic helper == */ - -#ifdef LIB_FORMAT_UTIL_H -namespace lib { - template - Variant::operator string() const - { - return string(buffer()); - } + /** workaround for GCC 4.7: need to exclude some types, + * since they raise private access violation during probing. + * Actually, in C++11 such a case should trigger substitution + * failure, not an compilation error */ + template + struct can_use_assignment + : is_copy_assignable + { }; - template - template - Variant::Buff::operator string() const - { - return "Variant|" - + util::str (this->access(), - (util::tyStr()+"|").c_str() - ); - } -}// namespace lib - -#endif + template<> + struct can_use_assignment + { static constexpr bool value = false; }; + + + + template + struct use_if_supports_only_move + : enable_if< is_move_constructible::value + && !is_copy_constructible::value + && !can_use_assignment::value + > + { }; + + template + struct use_if_supports_cloning + : enable_if< is_move_constructible::value + && is_copy_constructible::value + && !can_use_assignment::value + > + { }; + + template + struct use_if_supports_copy_and_assignment + : enable_if< is_move_constructible::value + && is_copy_constructible::value + && can_use_assignment::value + > + { }; + + + + + /** Policy to pick a suitable implementation of "virtual copy operations". + * @note You need to mix in the VirtualCopySupportInterface + */ + template + struct CopySupport + { + template + using Policy = NoCopyMoveSupport; + }; + + template + struct CopySupport::type> + { + template + using Policy = MoveSupport; + }; + + template + struct CopySupport::type> + { + template + using Policy = CloneSupport; + }; + + template + struct CopySupport::type> + { + template + using Policy = FullCopySupport; + }; + + + + +}} // namespace lib::meta #endif /*LIB_META_VIRTUAL_COPY_SUPPORT_H*/ diff --git a/src/lib/variant.hpp b/src/lib/variant.hpp index 3dca31677..13a80091e 100644 --- a/src/lib/variant.hpp +++ b/src/lib/variant.hpp @@ -61,6 +61,7 @@ ** ** @see Veriant_test ** @see lib::diff::GenNode + ** @see virtual-copy-support.hpp ** */ @@ -72,8 +73,11 @@ #include "lib/meta/typelist.hpp" #include "lib/meta/typelist-util.hpp" #include "lib/meta/generator.hpp" +#include "lib/meta/virtual-copy-support.hpp" #include +#include +#include namespace lib { @@ -89,12 +93,7 @@ namespace lib { namespace { // implementation helpers - using std::is_constructible; - using std::is_move_constructible; - using std::is_copy_constructible; - using std::is_copy_assignable; using std::remove_reference; - using std::enable_if; using meta::NullType; using meta::Node; @@ -126,9 +125,18 @@ namespace lib { + template + struct ValueAcceptInterface + { + virtual void handle(VAL&) { /* NOP */ }; + }; + + template + using VisitorInterface + = meta::InstantiateForEach; + + /////TODO: *nur noch* - /////TODO: - CopySuport in eigenen Header - /////TODO: - diesen mit Unit-Test covern /////TODO: - den gefährlichen Cast aus AccessCasted weg /////TODO: - *nur* der Unit-Test für OpaqueBuffer ist betroffen. Diesen durch direkten statischen Cast ersetzen /////TODO: - unit-Test für AccessCasted nachliefern @@ -168,7 +176,7 @@ namespace lib { private: /** Inner capsule managing the contained object (interface) */ struct Buffer - : VirtualCopySupportInterface + : meta::VirtualCopySupportInterface { char content_[SIZ]; @@ -347,7 +355,7 @@ namespace lib { using RawType = typename remove_reference::type; static_assert (meta::isInList(), "Type error: the given variant could never hold the required type"); - static_assert (can_use_assignment::value, "target type does not support assignment"); + static_assert (meta::can_use_assignment::value, "target type does not support assignment"); buff() = forward(x); return *this; diff --git a/tests/12metaprogramming.tests b/tests/12metaprogramming.tests index 2580cdeda..39bce4b62 100644 --- a/tests/12metaprogramming.tests +++ b/tests/12metaprogramming.tests @@ -771,3 +771,7 @@ END +TEST "Virtual copying of opaque objects" VirtualCopySupport_test < + 2015, Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -38,6 +38,9 @@ using std::string; using std::cout; using std::endl; +using lumiera::error::LUMIERA_ERROR_LOGIC; +using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; + namespace lib { namespace meta { @@ -61,7 +64,30 @@ namespace test { int _CheckSum_ = 0; + + /** Interface for the Virtual copy operations. + * @remarks we define this explicitly here for the tests solely. + * In real use, you'd just mix in VirtualCopySupportInterface. + * But since we want to verify the text fixture in isolation, + * we use here empty base implementations instead of pure + * virtual functions, so we can always instantiate all + * test classes. + */ + class CopyInterface + { + public: + virtual void copyInto (void* ) const { /* NOP */ }; + virtual void moveInto (void* ) { /* NOP */ }; + virtual void copyInto (Interface&) const { /* NOP */ }; + virtual void moveInto (Interface&) { /* NOP */ }; + }; + + + /** + * The official Interface for our test class hierarchy + */ class Interface + : public CopyInterface { public: virtual ~Interface() { } @@ -69,6 +95,9 @@ namespace test { virtual bool empty() const =0; }; + /** + * implementation class with "special" memory layout + */ template class Sub : public Interface @@ -88,7 +117,8 @@ namespace test { return storage_[i-1]; } - public: + public: /* == Implementation of the Interface == */ + virtual operator string() const override { return _Fmt("Sub|%s|%d|-%s") @@ -103,6 +133,9 @@ namespace test { return !bool(access()); } + + public: /* == full set of copy and assignment operations == */ + ~Sub() { _CheckSum_ -= access(); @@ -136,6 +169,7 @@ namespace test { Sub& operator= (Sub&& rsub) { + _CheckSum_ -= access(); access() = 0; std::swap(access(),rsub.access()); return *this; @@ -154,7 +188,7 @@ namespace test { class UnAssignable : public Sub { - void operator= (UnAssignable const&); + void operator= (UnAssignable const&); // private and unimplemented public: UnAssignable() = default; UnAssignable(UnAssignable&&) = default; @@ -180,9 +214,50 @@ namespace test { Noncopyable () = default; }; + + + /* == concrete implementation subclass with virtual copy support == */ + + template + class Opaque //-----Interface| CRTP-Impl | direct Baseclass + : public CopySupport::template Policy, IMP> + { + public: + static Opaque& + downcast (Interface& bas) + { + Opaque* impl = dynamic_cast (&bas); + + if (!impl) + throw error::Logic("virtual copy works only on instances " + "of the same concrete implementation class" + ,error::LUMIERA_ERROR_WRONG_TYPE); + else + return *impl; + } + }; + + // == Test subject(s)========================== + using RegularImpl = Opaque>; + using ClonableImpl = Opaque>; + using MovableImpl = Opaque>; + using ImobileImpl = Opaque>; + }//(End)Test fixture +// GCC 4.7 workaround +// SFINAE does not work properly on private functions +// instead of dropping the template instance, it causes compilation failure + +}//now in namespace meta + + template + struct can_use_assignment> + { static constexpr bool value = false; }; + +namespace test { + @@ -203,8 +278,28 @@ namespace test { verify_TestFixture(); CHECK(0 == _CheckSum_); + + verify_fullVirtualCopySupport(); + verify_noAssignementSupport(); + verify_onlyMovableSupport(); + verify_disabledCopySupport(); + + CHECK(0 == _CheckSum_); } + + /** @test our test fixture is comprised of + * - a common interface (#Interface) + * - a implementation template #Sub to hold a buffer and + * manage a distinct random value at some position in that buffer, + * which depends on the concrete implementation type + * - layered on top are adapters to make this implementation class + * either fully copyable, non-assignable, only movable or noncopyable. + * - a global checksum, based on the random value of all instances, + * which are incremented on construction and decremented on destruction. + * After destroying everything this checksum should go to zero. + * This test case just verifies this implementation mechanic. + */ void verify_TestFixture() { @@ -221,6 +316,12 @@ namespace test { CHECK (string(a) == string(aa)); CHECK (string(a) == string(a1)); + CHECK (!isnil(a1)); + + a = std::move(a1); + + CHECK (isnil(a1)); + CHECK (string(a) == string(aa)); /* == interface vs. concrete class == */ @@ -275,6 +376,168 @@ namespace test { CHECK (!isnil (e)); } + + + + + + + void + verify_fullVirtualCopySupport() + { + RegularImpl a,aa,aaa; + Interface& i(a); + Interface& ii(aa); + Interface& iii(aaa); + + char storage[sizeof(RegularImpl)]; + Interface& iiii (*reinterpret_cast (&storage)); + + string prevID = a; + CHECK (!isnil (a)); + + i.moveInto(&storage); + CHECK (string(iiii) == prevID); + CHECK (!isnil(iiii)); + CHECK ( isnil(i)); + + ii.copyInto(i); + CHECK (!isnil(i)); + CHECK (!isnil(ii)); + CHECK (string(i) == string(ii)); + + prevID = iii; + iii.moveInto(ii); + CHECK (!isnil(ii)); + CHECK ( isnil(iii)); + CHECK (string(ii) == prevID); + + // Verify that type mismatch in assignment is detected... + Opaque> divergent; + Interface& evil(divergent); + VERIFY_ERROR (WRONG_TYPE, evil.copyInto(i)); + VERIFY_ERROR (WRONG_TYPE, evil.moveInto(i)); + + + cout << "==fullVirtualCopySupport=="< (&storage)); + + string prevID = b; + CHECK (!isnil (b)); + + i.moveInto(&storage); + CHECK (string(iiii) == prevID); + CHECK (!isnil(iiii)); + CHECK ( isnil(i)); + + iiii.~Interface(); //clean-up previously placed instance + + prevID = ii; + ii.copyInto(&storage); + CHECK (!isnil(ii)); + CHECK (!isnil(iiii)); + CHECK ( isnil(i)); + CHECK (string(iiii) == prevID); + CHECK (string(ii) == prevID); + + prevID = iii; + VERIFY_ERROR (LOGIC, iii.copyInto(ii)); + VERIFY_ERROR (LOGIC, iii.moveInto(ii)); + CHECK (string(iii) == prevID); + CHECK (!isnil(iii)); + + cout << "==noAssignementSupport=="< (&storage)); + + string prevID = i; + CHECK (!isnil (i)); + + i.moveInto(&storage); + CHECK (string(iiii) == prevID); + CHECK (!isnil(iiii)); + CHECK ( isnil(i)); + + prevID = ii; + VERIFY_ERROR (LOGIC, ii.copyInto(&storage)); + VERIFY_ERROR (LOGIC, ii.copyInto(i)); + VERIFY_ERROR (LOGIC, ii.moveInto(i)); + CHECK (string(ii) == prevID); + CHECK (!isnil(ii)); + CHECK ( isnil(i)); + + cout << "==onlyMovableSupport=="<