优雅的方式来强制具有相同属性的矢量元素

时间:2011-05-27 10:09:36

标签: c++ vector

标题有点模糊,但我无法提出更好的措辞,这是交易:

class A
{
public:
  A();
  A( const PropertyB& b );
  PropertyA GetPropertyA();
  PropertyB GetPropertyB();
  SetPropertyA( const PropertyA& b );
  SetPropertyB( const PropertyB& b );
  //assignment not allowed
};

假设我想使用std :: vector< A>,但是,如果它的所有元素具有相同的PropertyB值,则只有A的向量才有意义。当前的解决方案是提供类似控制器的方法来创建这样的数组,该方法保证返回数组的所有元素具有相同的PropertyB值,以及检查是否是这种情况的方法:

Array MakeArray( size_t, const PropertyB& );
bool CheckIfArrayIsSane( const Array& );

所以用户仍然可以在元素上调用SetPropertyB(),但有一个实用程序可以检查它,如果有人这样做就会挽救:

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();

虽然这很有效,但它容易出错,因为很难在任何地方强制执行此检查而不会忘记它,并且会使代码混乱并带有支票。

工作的Approcahes:

  • 使SetPropertyB()变为私有,并使MakeArray成为友方函数:那么对于那些只想使用A而不关心数组的用户来说,不再可以访问SetPropertyB()。
  • 包装std :: vector< A>在一个单独的类中,只返回const A&引用:这意味着其他setter如SetPropertyA()也无法调用,但用户应该能够调用它们。它只是禁止的SetPropertyB()。

一种更具侵入性的方法可行,但感觉有点不雅,需要额外的功能才能在两者之间进行转换等:

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;

这个问题最优雅的解决方案是什么?

6 个答案:

答案 0 :(得分:2)

从OO设计的角度来看,这没有意义。还记得Liskov Susbsitution原则:只要A对象可以用在B对象的位置,A是-A B?根据该规则,您提议的std::vector<A>的元素未通过is-an-A测试,因为您无法设置其B属性。然而,std::vector<A>应该是A对象的容器。

在C ++中,我们有私有继承,这明确表示派生类与其父类具有is-a关系。您可以按如下方式使用它:

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;
 };

您现在可以创建一个std::vector<A_fixed_B>,其行为与您可能期望的一样。

答案 1 :(得分:1)

我会说的:

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(); }
};

如果您的A对象是由代理实例化的,您可以直接使用std::vector< std::autoptr<AI> >;

答案 2 :(得分:1)

更容易做的事情是包装std::vector<A>并返回某种形式的代理对象。

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)); 
    }
};

这真的很难看,但它会起作用。我猜你还需要包装迭代器和at()。但是,A本身并不需要知道任何相关内容,而正常的std::vector<A>完全不受影响。

答案 3 :(得分:1)

如果在分配后flyweight中元素的数量大于1,则使用flyweight存储PropertyB并在SetPropertyB中抛出异常。

无论您在哪个容器中存储As,都不允许使用不同的PropertyB。

#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();
}

实际上没有测试过这段代码的编译方式,但是它给了你一个想法。

答案 4 :(得分:0)

我会选择“Wrapping std :: vector&lt; A&gt; ...”方法。这样,类的用户将无法修改vector中的对象,就好像他们想要调用SetPropertyB一样,他们可以创建从此包装器返回的对象的副本并使用该方法

答案 5 :(得分:0)

使用策略基类来确定可以设置哪些字段 - 如果您尝试设置错误的属性,这将生成编译时错误。当然,缺点是A不再仅仅是A,而是在政策上输入。

代码:

#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);

}

演示:http://www.ideone.com/mTJSb

因此,我们的想法是使用继承,A的基类将公开可以设置的内容。在AllPolicy情况下,两种方法都被暴露,而在另一种情况下,暴露了一个或另一个设置器。如果您尝试使用与策略相反的setter,则应该产生编译器错误(如在演示中)。当然,现在A<APolicy> A<BPolicy>不同,如果您想要转换,则必须提供转换构造函数等。