Elegant way to force vector elements having same properties
The title is a bit vague but I can't come up with a better wording, here's the deal:
class A
{
public:
A();
A( const PropertyB& b );
PropertyA GetPropertyA();
PropertyB GetPropertyB();
SetPropertyA( const PropertyA& b );
SetPropertyB( const PropertyB& b );
//assignment not allowed
};
Suppose I want to use an std::vector< A >, but, it only makes sense to have a vector of A if all it's elements have the same value for PropertyB. The current solutions is to supply a contrcutor-like method to create such array which guarantees all elements of the returned array have the same value for PropertyB, and a method that checks开发者_高级运维 if that is the case:
Array MakeArray( size_t, const PropertyB& );
bool CheckIfArrayIsSane( const Array& );
So users can still call SetPropertyB() on the elements, but have a utility to check it and bail out if someone did:
Array x( MakeArray( 3, someValue ) );
x.SetPropertyA( aaa ); //should be allowed
x.SetPropertyB( someOtherValue ); //should NOT be allowed
//somewhat further in the program
if( !CheckIfArrayIsSane( x ) )
throw InsaneArrayExcpetion();
While this works, it's error-prone since it is hard to force this check everywhere and not forget it, and clutters the code with checks.
Approcahes that do not work:
- Making SetPropertyB() private, and making MakeArray a friend function: then SetPropertyB() is not accessible anymore for users that simply want to use A without caring about the array.
- Wrapping std::vector< A > in a seperate class and only returning const A& references: this would mean the other setters like SetPropertyA() can also not be called, but users should be able to call them. It's only SetPropertyB() that should be disallowed.
One more intrusive approach that would work but feels a bit unelegant, and needs extra functions to convert between the two etc:
class AWithFixedPropertyB
{
public:
A( const PropertyB& b );
PropertyA GetPropertyA();
PropertyB GetPropertyB();
SetPropertyA( const PropertyA& b );
};
class A : public AWithFixedPropertyB
{
public:
//contrcutors etc
SetPropertyB( const PropertyB& b );
};
//ok, users cannot modify property B in this Array
typedef std::vector< AWithFixedPropertyB > Array;
What would be the most elegant solution for this problem?
This doesn't make sense, from an OO design viewpoint. Remember the Liskov Susbsitution Principle: A is-a B whenever an A object can be used in the place of a B object? Per that rule, the elements of your proposed std::vector<A>
fail the is-an-A test, since you can't set their B property. Yet std::vector<A>
is supposed to be a container of A objects.
In C++ we have private inheritance, and this makes explicit that the derived class does not have an is-a relationship with its parent. You can use that as follows:
class A_fixed_B : private A
{
A_fixed_B(A const& src) : A(src) {}
A const& asA() const { return *this; } // Base Conversion is OK inside class.
using A::GetPropertyA;
using A::GetPropertyB;
using A::SetPropertyA;
};
You can now create a std::vector<A_fixed_B>
which behaves as you'd probably expect.
The easier thing to do would be to wrap std::vector<A>
and return some form of proxy object.
class WrappedVectorA : private std::vector<A> {
struct MyProxy {
MyProxy(A& ref) { ptr = &ref; }
A* ptr;
PropertyA GetPropertyA() const { return ptr->GetPropertyA(); }
PropertyB GetPropertyB() const { return ptr->GetPropertyB(); }
SetPropertyA( const PropertyA& b ) { return ptr->SetPropertyA(b); }
operator=(const A& a) { *ptr = a; }
operator const A&() const { return *ptr; }
operator A() { return *ptr; }
};
public:
MyProxy operator[](int index) {
return std::vector<A>::operator[](index);
}
const MyProxy operator[](int index) const {
return const_cast<A&>(std::vector<A>::operator[](index));
}
};
It's really kind of ugly, but it'll work. I guess you'd also need to wrap the iterators and at()
. However, A
itself doesn't need to know anything about this, and normal std::vector<A>
are completely untouched.
I'll go with that :
class IA {
public:
virtual ~IA() {}
virtual PropertyA& GetPropertyA() = 0;
virtual void SetPropertyA(const PropertyA& a) = 0;
};
class A : public IA
{
public:
A();
A( const PropertyB& b );
PropertyA& GetPropertyA();
PropertyB GetPropertyB();
void SetPropertyA( const PropertyA& b );
void SetPropertyB( const PropertyB& b );
};
template< PropertyB value >
class fixedVector {
private:
std::vector<A> _tab;
public:
void pushback() {_tab.pushback(A(value)); }
IA& get(unsigned int i) { return _tab[i]; }
void popback() { _tab.pop_back(); }
};
If your A object are instanciate by a proxy, you could use a directly a std::vector< std::autoptr<AI> >;
Use a flyweight for the storage of PropertyB and throw an exception in SetPropertyB, if the count of elements in the flyweight is greater than 1 after the assignment.
Different PropertyBs will not be allowed no matter which container you store the As in.
#include <boost/flyweight.hpp>
class A
{
public:
A();
A( const PropertyB& b );
PropertyA GetPropertyA();
PropertyB GetPropertyB();
SetPropertyA( const PropertyA& b );
SetPropertyB( const PropertyB& b );
private:
PropertyA a;
boost::flyweights::flyweight<PropertyB> b;
};
A::SetPropertyB(const PropertyB& item)
{
b = item;
if(b.size() > 1) // you may have to implement flyweight::size() yourself
// should be able to do this based on core::factory().size();
throw InsaneArrayExcpetion();
}
Haven't actually tested if this code compiles, but it gives you the idea.
I would go with "Wrapping std::vector< A >..." approach. In that way user of the class will not be able to modify the objects in the vector
where as if they want to call SetPropertyB
they can create a copy of the object returned from this wrapper and use that method.
Use a policy base class to determine which fields can be set - this will generate a compile time error if you try to set the wrong property. The downside of course is that A
is no longer simply A
, but is typed on the policy.
Code:
#include <iostream>
#include <vector>
#include <boost/utility.hpp>
#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>
class AllPolicy
{
public:
void setA(int a) { _setA(a); }
void setB(int b) { _setB(b); }
private:
virtual void _setA(int a) = 0;
virtual void _setB(int b) = 0;
};
class APolicy
{
public:
void setA(int a) { _setA(a); }
typedef void setB;
private:
virtual void _setA(int a) = 0;
};
class BPolicy
{
public:
void setB(int b) { _setB(b); }
typedef void setA;
private:
virtual void _setB(int b) = 0;
};
template <typename Policy>
class A : public Policy
{
public:
A(int a = 0, int b = 0) : _a(a), _b(b)
{
}
A(A const& v) : _a(v._a), _b(v._b)
{
}
~A() {}
A& operator=(A const& v)
{
_a = v._a;
_b = v._b;
return *this;
}
int getA() const { return _a; }
int getB() const { return _b; }
using Policy::setA;
using Policy::setB;
private:
virtual void _setA(int a) { _a = a; }
virtual void _setB(int b) { _b = b; }
private:
int _a;
int _b;
};
int main(void)
{
std::vector<A<AllPolicy> > all_v(1, A<AllPolicy>(2, 3));
all_v[0].setA(1);
all_v[0].setB(2);
std::vector<A<APolicy> > a_v(1, A<APolicy>(2));
a_v[0].setA(1);
a_v[0].setB(2);
std::vector<A<BPolicy> > b_v(1, A<BPolicy>(1, 3));
b_v[0].setA(1);
b_v[0].setB(2);
}
Demo: http://www.ideone.com/mTJSb
So the idea is to use inheritance and the base class of A
will expose what can be set. In the AllPolicy
case, both methods are exposed, and in the other case one or other of the setters are exposed. A compiler error should result (as in the demo) if you try to use the opposite setter to the policy. Of course now A<APolicy>
is not the same as A<BPolicy>
, and if you wanted conversion, you'll have to provide conversion constructors etc.
精彩评论