Элегантный способ заставить векторные элементы иметь одинаковые свойства - PullRequest
0 голосов
/ 27 мая 2011

Название немного расплывчато, но я не могу придумать лучшую формулировку, вот предложение:

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.Текущее решение состоит в том, чтобы предоставить метод, подобный контролю, для создания такого массива, который гарантирует, что все элементы возвращаемого массива имеют одинаковое значение для 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();

Хотя это работает, оно подвержено ошибкам, так как это трудно заставитьпроверяйте везде и не забывайте об этом и загромождайте код проверками.

Приближения, которые не работают:

Еще один навязчивый подход, который бы работал, но чувствовал себя немного не элегантно и нуждался в дополнительных функциях для преобразования между ними и т.д .:

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 ]

2 голосов
/ 27 мая 2011

Это не имеет смысла, с точки зрения дизайна ОО.Помните принцип подстановки Лискова: A is-A B всякий раз, когда объект 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 голос
/ 29 мая 2011

Используйте мухи для хранения PropertyB и создайте исключение в SetPropertyB, если количество элементов в мухе больше 1 после назначения.

Разные PropertyB не будут разрешены независимо от того, в каком контейнере вы храните As.

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

На самом деле не проверял, компилируется ли этот код, но он дает вам идею.

1 голос
/ 27 мая 2011

Проще всего было бы обернуть 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> совершенно не тронуты.

1 голос
/ 27 мая 2011

Я пойду с этим:

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

напрямую
0 голосов
/ 27 мая 2011

Используйте базовый класс политики, чтобы определить, какие поля можно установить - это приведет к ошибке времени компиляции, если вы попытаетесь установить неправильное свойство. Недостатком, конечно, является то, что 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 оба метода открыты, а в другом случае - один или другой из сеттеров. Ошибка компилятора должна появиться (как в демоверсии), если вы попытаетесь использовать противоположный установщик к политике. Конечно, теперь A<APolicy> - это не то же самое, что A<BPolicy>, и если вы хотите преобразование, вам нужно будет предоставить конструкторы преобразования и т. Д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...