Reputation: 2117
Here's a set of C++ classes which implement a kind of adapter pattern:
#include <iostream>
class Cfoo
{
public:
explicit Cfoo(int i):i_(i){}
void SetI(int i){ i_ = i; }
int GetI()const{ return(i_); }
private:
int i_;
};
class CfooHolderConst
{
public:
explicit CfooHolderConst(const Cfoo& foo):foo_(foo){}
int GetI()const{ return( foo_.GetI() ); }
private:
const Cfoo& foo_;
};
class CfooHolderNonConst
{
public:
explicit CfooHolderNonConst(Cfoo& foo):foo_(foo){};
int GetI()const{ return( foo_.GetI() ); }
void SetI(int i){ foo_.SetI(i); }
private:
Cfoo& foo_;
};
int main( int argc, char* argv[] )
{
const Cfoo myConstFoo(42);
CfooHolderConst myConstFooHolder(myConstFoo);
std::cout << myConstFooHolder.GetI() << std::endl;
Cfoo myNonConstFoo(1);
CfooHolderNonConst myNonConstFooHolder(myNonConstFoo);
myNonConstFooHolder.SetI(42);
std::cout << myConstFooHolder.GetI() << std::endl;
return(0);
}
I want to combine CfooHolderNonConst and CFooHolderConst into a single class, or failing that, inherit one from the other. The reference to Cfoo is a problem here, because in CFooHolderConst it needs to be defined as const Cfoo&, while in CfooHolderNonConst it needs to be Cfoo&.
This is a similar problem to the interator/const_iterator here: How to avoid code duplication implementing const and non-const iterators?
...but I'm hoping that because this doesn't have to meet the STL iterator requirements, there might be a simpler solution.
In the past I've solved this kind of problem by having both a const and nonconst pointer as class members, and setting one or the other up from overloaded constructors. This wastes space and seems clumsy. Is there a more elegant solution?
Upvotes: 0
Views: 293
Reputation: 67723
Is there a specific reason why you can't just use FooHolder
for non-const (mutable) access, and const FooHolder
for const access?
You can't call a non-const-qualified method (like SetI
) on a const object, so it seems like it does what you want. Obviously you need to create the holder object from a non-const Cfoo
originally, though.
example:
class Cfoo
{
public:
explicit Cfoo(int i) : i_(i) {}
void SetI(int i) { i_ = i; }
int GetI() const { return(i_); }
private:
int i_;
};
class CfooHolder
{
public:
explicit CfooHolder(Cfoo& foo) : foo_(foo) {};
void SetI(int i) { foo_.SetI(i); }
int GetI() const { return( foo_.GetI() ); }
private:
Cfoo& foo_;
};
void bar(CfooHolder &holder, int i)
{
holder.SetI(i); // fine
}
void bar(CfooHolder const &constholder, int i)
{
holder.SetI(i);
// error: method exists, but I can't call it here
}
Upvotes: 0
Reputation: 12212
If you really want to have the same class (providing the semantics still make sense), then I think you want something like:
const Cfoo f1( 5 );
const CfooHolder h1( f1 );
Cfoo f2( 0 );
CfooHolder h2( f2 );
I think that your hope would be for C++ to make the following decissions:
a) Treat the Cfoo object as const
if it was const
, or non-const if it was non-const. The clue is both the definition of Cfoo and the definition of CfooHolder. If Cfoo is const
, then CfooHolder must be declared const
, or it should fail to compile. If Cfoo is non-const, then you can create CfooHolder's which can be both const
and non-const
.
b) The method SetI()
should cease to compile when used in a const CfooHolder object. In the example above h1.SetI( 6 );
should not compile.
My answer is that, if a) worked, then b) would automatically work as well. The problem is achieving a), which is not possible as far as I know.
For this to work, the attribute should be made const
or non-const under the circunstance of an object of its class being const or non-const. Though an object of the class can change this "state", the attributes remain the same, however. But you can only use const
methods when the object of this class is const
(for example, when a parameter is passed by constant reference). So, C++ won't support that, because it does not work that way.
The other possibility would be to let the attribute itself be const and non-const at the same time, which does not make sense.
The short answer is: it can't be done and there will be code repetition. If you really want to avoid this, and the wrapper is enough complex to be worried, the only way is to create a general holder, and then the constant and non-constant wrappers around the general holder, avoiding repetition to the bare minimal.
class CfooHolder
{
public:
explicit CfooHolder(Cfoo& foo):foo_(foo){};
int GetI()const{ return( foo_.GetI() ); }
virtual void SetI(int i){ foo_.SetI(i); }
protected:
Cfoo& foo_;
};
class CfooHolderNonConst : public CfooHolder {
public:
explicit CfooHolderNonConst(Cfoo& foo):CfooHolder(foo){};
};
class CfooHolderConst: public CfooHolder
{
public:
explicit CfooHolderConst(const Cfoo& foo):CfooHolder(const_cast<Cfoo &>( foo )){}
void SetI(int i){ throw std::runtime_error( "Don't write to me!" ); }
};
It is not perfect, but it works under the stated terms. The method SetI()
would throw a runtime error, but if the CfooHolderConst object is declared as const
, then a call to SetI()
won't even compile.
Hope this helps.
Upvotes: 0
Reputation: 31435
Yes, it can be done:
template< typename T > CHolderReader
{
public:
explicit CHolderBase( T& t):t_(t){}
int Get()const { return t_.GetI(); }
protected:
~CHolderReader() {}
protected:
T& t_;
};
template< typename T > CHolderReaderWriter : public CHolderReader< T >
{
public:
void Set( int i)
{
t_.SetI(i);
}
};
typedef CHolderReader<const Cfoo> CFooHolderConst;
typedef CHolderReaderWriter<Cfoo> CFooHolderNonConst;
Actually this is intended to be an example where you wrap the getting of the underlying data in its const or non-const state. The Reader holds a non-const reference unless the templated type is const, but doesn't let you write to it, so you can extend it as with CHolderReaderWriter when you do need to write to it.
Upvotes: 1
Reputation: 740
You can create a template class that will give you const functionality for both const and non-const versions of the class, and then inheritance to extend the non-const version with functionality to modify the member:
class Cfoo
{
public:
explicit Cfoo(int i):i_(i){}
void SetI(int i){ i_ = i; }
int GetI()const{ return(i_); }
private:
int i_;
};
class CfooHolderNonConst;
template<class Foo>
class CFooHolder
{
friend class CfooHolderNonConst;
public:
explicit CFooHolder(Foo& foo):foo_(foo){}
int GetI()const{ return( foo_.GetI() ); }
private:
Foo& foo_;
};
typedef CFooHolder<const Cfoo> CfooHolderConst;
class CfooHolderNonConst: public CFooHolder<Cfoo>
{
public:
explicit CfooHolderNonConst(Cfoo& foo):CFooHolder(foo){};
void SetI(int i){ foo_.SetI(i); }
};
Upvotes: 1
Reputation: 8053
I think it is good idea to have both const- and non-const- interface as a separate classes.
They provide different interfaces to their users and have different semantics. The duplication is also minimal in your example.
Upvotes: 0