Использование класса с константными членами данных в векторе - PullRequest
10 голосов
/ 27 мая 2010

Учитывая класс как это:

class Foo
{
   const int a;
};

Можно ли поместить этот класс в вектор? Когда я пытаюсь, мой компилятор говорит мне, что он не может использовать оператор присваивания по умолчанию. Я пытаюсь написать свой собственный, но поиск в Google подсказывает мне, что невозможно написать оператор присваивания для класса с постоянными членами-данными. В одном сообщении, которое я нашел, говорилось, что «если вы сделали [элемент данных] const, это означает, что вы не хотите, чтобы назначение происходило в первую очередь». Это имеет смысл. Я написал класс с константными членами данных, и я никогда не намеревался использовать присваивание для него, но, очевидно, мне нужно присваивание, чтобы поместить его в вектор. Есть ли способ обойти это, который все еще сохраняет константность?

Ответы [ 4 ]

8 голосов
/ 27 мая 2010

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

Вы должны спросить, выполняется ли следующее ограничение

a = b;
 /* a is now equivalent to b */

Если это ограничение не верно для a и b типа Foo (вы должны определить семантику того, что означает "эквивалент"!), То вы просто не можете поместить Foo в стандарт контейнер. Например, auto_ptr нельзя помещать в стандартные контейнеры, поскольку это нарушает это требование.

Если вы можете сказать о вашем типе, что он удовлетворяет этому ограничению (например, если член const никоим образом не участвует в значении вашего объекта, но в любом случае подумает о том, чтобы сделать его статическим членом данных), то вы можете написать свой собственный оператор присваивания

class Foo
{
   const int a;
public:
   Foo &operator=(Foo const& f) {
     /* don't assign to "a" */
     return *this;
   }
};

Но подумайте дважды! . Мне кажется, что ваш тип не не удовлетворяет ограничению!

3 голосов
/ 27 мая 2010

Использовать вектор указателей std::vector<Foo *>. Если вы хотите избежать трудностей с уборкой после себя, используйте boost::ptr_vector.

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

Редактировать : мой начальный удар во время моего перерыва на кофе, static const int a; не будет работать для случая использования, который имеет в виду ОП, что подтверждают первоначальные комментарии, поэтому я переписываю и расширяю свойответ.

В большинстве случаев, когда я хочу сделать элемент класса константным, это константа, значение которой постоянно для всех времен и для всех экземпляров класса.В этом случае я использую статическую переменную const:

class Foo
{
public:
    static const int a;
};

Их не нужно копировать между экземплярами, поэтому, если она будет применена, это решит вашу проблему с назначением.К сожалению, OP указал, что это не будет работать в том случае, если он имеет в виду.

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

class Foo
{
public:
    int get_a() const { return a; }
private:
    int a;
};

Разница между этим и

class Foo
{
public:
    const int a;
};

:

  • * * * * * * * * * * * * * * * * * * * * * * * * * * * 10 * * * * * * * * * * * * * * * * * * * * * 10 10 * * * * 10 10Это означает, что назначение по праву не будет работать, так как это будет пытаться изменить значение a после создания объекта.(Вот почему, кстати, написание пользовательского operator=(), который пропускает копию a, вероятно, плохая идея с точки зрения дизайна.)
  • Доступ другой - вы должны скорее пройти через геттерчем доступ к члену напрямую.

На практике при выборе между этими двумя я использую элементы только для чтения.Это, вероятно, означает, что вы сможете заменить значение объекта значением другого объекта, не нарушая семантику вообще.Давайте посмотрим, как это будет работать в вашем случае.

Рассмотрим ваш объект Grid с шириной и высотой.Когда вы изначально создаете вектор, и допустим, что вы зарезервировали некоторое начальное пространство, используя vector::reserve(), ваш вектор будет заполнен начальными по умолчанию (то есть пустыми) сетками.Когда вы идете, чтобы назначить определенную позицию в векторе, или вставляете Grid в конец вектора, вы заменяете значение объекта в этой позиции на Grid, который имеет фактический материал.Но вы можете быть в порядке с этим!Если причина, по которой вы хотели, чтобы ширина и высота были постоянными, на самом деле заключается в обеспечении согласованности между шириной и высотой и остальным содержимым вашего объекта Grid, и вы убедились, что не имеет значения, является ли ширинаи высота заменяются до или после замены других элементов Grid, тогда это назначение должно быть безопасным, потому что к концу назначения все содержимое экземпляра будет заменено, и вы вернетесь в согласованное состояние.(Если отсутствие атомарности присваивания по умолчанию было проблемой, вы, вероятно, могли бы обойти это, реализовав свой собственный оператор присваивания, который использовал конструктор копирования и операцию swap().)

В итоге, вы получаете, используя геттеры только для чтения, это возможность использовать объекты в векторе или любом контейнере с семантикой значений.Однако затем вы должны убедиться, что ни одна из внутренних операций Grid (или операций друзей Grid) не нарушает эту последовательность, потому что компилятор не будет блокировать ширину и высоту для вас.Это также относится к построению по умолчанию, копированию и назначению.

0 голосов
/ 27 мая 2010

Я рассматриваю возможность сделать член данных неконстантным, но закрытым и доступным только для функции get, например:

class Foo
{
    private:
        int a;
    public:
        int getA() const {return a;}
};

Это так же хорошо, как const? Есть ли у него недостатки?

...