Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@hostilefork
Created July 13, 2012 19:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hostilefork/3106817 to your computer and use it in GitHub Desktop.
Save hostilefork/3106817 to your computer and use it in GitHub Desktop.
Preserving Const-Correctness of an Implementation class when Creating Wrappers
// 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;
}
// 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