Created
July 13, 2012 19:23
-
-
Save hostilefork/3106817 to your computer and use it in GitHub Desktop.
Preserving Const-Correctness of an Implementation class when Creating Wrappers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// const-wrapper-test.cpp | |
// | |
// For motivation and argumentation, reference: | |
// http://stackoverflow.com/questions/11167483/deriving-from-a-base-class-whose-instances-reside-in-a-fixed-format-database-m | |
// http://stackoverflow.com/questions/11219159/make-interchangeable-class-types-via-pointer-casting-only-without-having-to-all | |
#include <iostream> | |
#include <cassert> | |
#include "const-wrapper.h" | |
using namespace std; | |
class MyAccessor : public Accessor { | |
public: | |
int getSquareOfBar() const { | |
return getBar() * getBar(); | |
} | |
void setBarToSquareOfBar() { | |
setBar(getSquareOfBar()); | |
} | |
}; | |
class MyFakeAccessor { | |
public: | |
void setFooPtr(Foo * newFooPtr) { | |
cout << "Ha, tricked you! Not derived from accessor!" << endl; | |
} | |
Foo & getFoo() { throw "Ha ha!"; } | |
}; | |
int main(int argc, char* argv[]) { | |
unique_ptr<Foo> theFoo (Foo::create(1020)); | |
// Spoofing accessor test | |
#if DEMONSTRATE_ERROR_CASES | |
Wrapper<MyFakeAccessor> testWrapSpoof (*theFoo); | |
#endif | |
// Method privilege enforcement tests | |
Wrapper<MyAccessor const> constWrap (*theFoo); | |
Wrapper<MyAccessor> normalWrap (*theFoo); | |
assert(constWrap->getBar() == 1020); | |
#if DEMONSTRATE_ERROR_CASES | |
constWrap->setBarToSquareOfBar(); | |
#endif | |
assert(constWrap->getSquareOfBar() == 1020 * 1020); | |
normalWrap->setBarToSquareOfBar(); | |
assert(normalWrap->getBar() == 1020 * 1020); | |
// Argument compatibility tests | |
[](Wrapper<MyAccessor const>) { } (constWrap); | |
#if DEMONSTRATE_ERROR_CASES | |
[](Wrapper<MyAccessor>) { } (constWrap); | |
#endif | |
[](Wrapper<MyAccessor const>) { } (normalWrap); | |
[](Wrapper<MyAccessor>) { } (normalWrap); | |
// Assignment compatibility tests | |
unique_ptr<Foo> theFoo2 (Foo::create(304)); | |
Wrapper<MyAccessor const> constWrap2 (*theFoo2); | |
Wrapper<MyAccessor> normalWrap2 (*theFoo2); | |
constWrap2 = constWrap; | |
#if DEMONSTRATE_ERROR_CASES | |
normalWrap2 = constWrap; | |
#endif | |
constWrap2 = normalWrap; | |
normalWrap2 = normalWrap; | |
// Assignment correctness tests | |
assert(constWrap2->getBar() == normalWrap->getBar()); | |
assert(normalWrap2->getBar() == normalWrap->getBar()); | |
return 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// const-wrapper.h | |
// | |
// For motivation and argumentation, reference: | |
// http://stackoverflow.com/questions/11167483/deriving-from-a-base-class-whose-instances-reside-in-a-fixed-format-database-m | |
// http://stackoverflow.com/questions/11219159/make-interchangeable-class-types-via-pointer-casting-only-without-having-to-all | |
#ifndef CONST_WRAPPER_H | |
#define CONST_WRAPPER_H | |
#include <memory> | |
// Here Foo is the class which we cannot meaningfully inherit from, but | |
// we wish to be able to augment with something like a Proxy pattern | |
// http://en.wikipedia.org/wiki/Proxy_pattern | |
class Foo final { | |
private: | |
int bar; | |
private: | |
// Not constructed directly by clients. | |
// (ideally, should only be destroyed by unique_ptr) | |
Foo(int initialBarValue) : bar (initialBarValue) { } | |
public: | |
static std::unique_ptr<Foo> create(int initialBarValue) { | |
return std::unique_ptr<Foo> (new Foo (initialBarValue)); | |
} | |
~Foo () { } // final class, destructor need not be virtual | |
public: | |
// There are NC non constant operations on Foo. Here, let NC=1 | |
void setBar(int newBarValue) { bar = newBarValue; } | |
public: | |
// There are C constant operations on Foo. Here, let C=1 | |
int getBar() const { return bar; } | |
}; | |
// Accessor is the base for our proxy classes. They provide a compound | |
// interface to Foo. If you inherit publicly from it, then your wrapped | |
// entity will also offer the Foo interface functions...if you inherit protected | |
// then you will only offer your own interface. | |
class Accessor { | |
private: | |
// Accessor instances are not to assume they will always be processing | |
// the same Foo. But for implementation convenience, the wrapper | |
// pokes the Foo in before calling any methods on the Accessor | |
mutable Foo * fooPtrDoNotUseDirectly; | |
template<class AccessorType> friend class Wrapper; | |
void setFooPtr(Foo * newFooPtr) { | |
// only called by Wrapper. The reason this is not passed in | |
// the constructor is because it would have to come via | |
// the derived class, we want compiler default constructors | |
fooPtrDoNotUseDirectly = newFooPtr; | |
} | |
void setFooPtr(Foo const * newFooPtr) const { | |
// we know getFoo() will only return a const foo which will | |
// restore the constness. As long as fooPtr is not used | |
// directly in its non-const form by this class, we should | |
// not trigger undefined behavior... | |
fooPtrDoNotUseDirectly = const_cast<Foo *>(newFooPtr); | |
} | |
// Should only be called by this class and the wrapper | |
Foo & getFoo() { return *fooPtrDoNotUseDirectly; } | |
Foo const & getFoo() const { return *fooPtrDoNotUseDirectly; } | |
protected: | |
// accessors should not be constructible by anyone but the | |
// wrapper class (any way to guarantee that?) | |
Accessor() {} | |
public: | |
// These functions should match Foo's public interface | |
// Library maintainer must keep NC + C members here updated | |
// (Not ideal, but manageable.) | |
inline void setBar(int newBarValue) { getFoo().setBar(newBarValue); } | |
inline int getBar() const { return getFoo().getBar(); } | |
}; | |
template<class AccessorType> class Wrapper; | |
// Wrapper for const case, -> returns a const Accessor * | |
template<class AccessorType> | |
class Wrapper<AccessorType const> { | |
static_assert(std::is_base_of<Accessor, typename std::remove_const<AccessorType>::type >::value, | |
"Wrapper<> may only be parameterized with a class derived from Accessor"); | |
protected: | |
// if const, assignment would be ill formed. But we don't want to | |
// accidentally invoke any non-const methods, as the foo pointer we | |
// passed in was const. | |
AccessorType accessorDoNotUseDirectly; | |
private: | |
inline AccessorType const & getAccessor() const { return accessorDoNotUseDirectly; } | |
public: | |
Wrapper () = delete; | |
Wrapper (Foo const & foo) { getAccessor().setFooPtr(&foo); } | |
AccessorType const * operator-> () const { return &getAccessor(); } | |
virtual ~Wrapper () { } | |
}; | |
// Wrapper for non-const case, -> returns a non-const Accessor * | |
template<class AccessorType> | |
class Wrapper : public Wrapper<AccessorType const> { | |
private: | |
inline AccessorType & getAccessor() { return Wrapper<AccessorType const>::accessorDoNotUseDirectly; } | |
public: | |
Wrapper () = delete; | |
Wrapper (Foo & foo) : Wrapper<AccessorType const> (foo) { } | |
AccessorType * operator-> () { return &Wrapper::getAccessor(); } | |
virtual ~Wrapper() { } | |
}; | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment