Постоянные члены класса, оператор присваивания и QList - PullRequest
5 голосов
/ 26 ноября 2010

Пожалуйста, подтвердите, если я прав, и скажите, есть ли лучшее решение:

Я понимаю, что объекты с постоянными членами, такими как int const width;, не могут быть обработаны искусственным оператором присваивания, который неявно создаетсякомпилятор.Но QList (и я полагаю, что std :: list тоже) нуждается в рабочем операторе присваивания.Поэтому, когда я хочу использовать объекты с постоянными членами и QList, у меня есть три возможности:

  1. Не использовать постоянные члены.(Не решение)
  2. Реализация собственного оператора присваивания.
  3. Использовать какой-то другой контейнер, для которого не нужны операторы присваивания

Это правильно?Существуют ли другие элегантные решения?

Также мне интересно, могу ли я:

  • (4) Заставить компилятор создать оператор присваивания, который имеет дело с постоянными членами!(Я не понимаю, почему это такая большая проблема. Почему оператор не достаточно умен для внутреннего использования списков инициализации? Или я что-то упускаю?)
  • (5) Скажите QList, что я никогда не буду использоватьоперации присваивания в списке.

РЕДАКТИРОВАТЬ: я никогда не назначаю объекты этого класса самостоятельно.Они создаются только конструктором копирования или перегруженным конструктором.Таким образом, оператор присваивания требуется только для контейнера, а не для меня.

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

Cell& Cell::operator=(Cell const& other)
{
 if (this != &other) {
  Cell* newCell = new Cell(other.column(), other.row());
  return *newCell;
 }
 return *this;
}

EDIT3: я нашел эту тему почти с тем же вопросом: C ++: проблемы STL с членами класса const Все ответыВ совокупности ответили на мои вопросы.

Ответы [ 4 ]

8 голосов
/ 27 ноября 2010

Вы, вероятно, новичок в C ++ и ожидаете, что он будет вести себя как Python, Java или C #.

Весьма распространено помещать неизменяемые объекты Java в коллекции.Это работает, потому что в Java вы на самом деле не помещаете Java объекты в коллекции, а просто Java ссылаются , которые ссылаются на объекты Java.Чтобы быть еще более точным, коллекция внутренне состоит из ссылочных переменных Java, и назначение этих ссылочных переменных Java вообще не влияет на объекты Java, на которые ссылаются.Они даже не замечают.

Я сознательно сказал «Java-объект», «Java-ссылка» и «Java-переменная», потому что термины «объект», «ссылка» и «переменная» имеют совершенно разные значения вC ++.Если вам нужны изменяемые T переменные, вам нужны изменяемые T объекты, потому что переменные и объекты в C ++ - это одно и то же:

Переменная вводится объявлением объекта.Имя переменной обозначает объект.

В C ++ переменные не содержат объектов - они являются объектами .Присвоение переменной означает изменение объекта (путем вызова функции-члена operator=).Обойти это невозможно.Если у вас есть неизменяемый объект, то присваивание a = b не может возможно работать без явного подрыва системы типов, и если вы это сделаете, то вы фактически солгали своим клиентам о неизменности объекта.Делать обещание, а затем сознательно нарушать его, довольно бессмысленно, не так ли?

Конечно, вы можете просто смоделировать способ Java: использовать набор указателей для неизменяемых объектов.Является ли это эффективным решением, зависит от того, что на самом деле представляют ваши объекты.Но то, что это хорошо работает в Java, не означает, что оно хорошо работает в C ++. В C ++ не существует такого понятия, как шаблон неизменяемого значения. Это хорошая идея в Java и ужасная идея в C ++.

Кстати, ваш оператор присваивания совершенно неидиоматическая и утечки памяти.Если вы серьезно относитесь к изучению C ++, вам следует прочитать одну из этих книг .

3 голосов
/ 26 ноября 2010

const не означает «это значение может измениться только при особых обстоятельствах».Скорее, const означает: «Ничто из того, что вам разрешено делать, не приведет к каким-либо изменениям (которые вы могли бы наблюдать)»

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

Но в вашем случае это постоянная переменная-член, это невозможно.Квалифицированная константная переменная не может быть константной ссылкой на неконстантную, потому что это вообще не ссылка.

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

int main() {
  const int i = 42; 
  const_cast<int&>(i) = 0; 
  return i;
}

А вот что излучает LLVM-G ++:

; ModuleID = '/tmp/webcompile/_2418_0.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
target triple = "x86_64-linux-gnu"

define i32 @main() nounwind {
entry:
  %retval = alloca i32                            ; <i32*> [#uses=2]
  %0 = alloca i32                                 ; <i32*> [#uses=2]
  %i = alloca i32                                 ; <i32*> [#uses=2]
  %"alloca point" = bitcast i32 0 to i32          ; <i32> [#uses=0]
  store i32 42, i32* %i, align 4
  store i32 0, i32* %i, align 4
  store i32 42, i32* %0, align 4
  %1 = load i32* %0, align 4                      ; <i32> [#uses=1]
  store i32 %1, i32* %retval, align 4
  br label %return

return:                                           ; preds = %entry
  %retval2 = load i32* %retval                    ; <i32> [#uses=1]
  ret i32 %retval2
}

Особый интерес представляет строка store i32 0, i32* %i, align 4.Это означает, что const_cast был успешным, мы фактически присвоили ноль над значением, к которому я был инициализирован.

Но изменения в константных квалификациях не могут привести к заметным изменениям.Таким образом, GCC создает довольно длинную цепочку, в которой 42 помещается в% 0, затем помещается 42 в% 1, затем снова сохраняется в% retval, а затем загружается в% retval2.Таким образом, G ++ будет иметь этот код, удовлетворяющий обоим требованиям, const был отброшен, но не было видимых изменений в i, main возвращает 42.


Если вам нужно значение, которое можно изменить,например, в элементах стандартного контейнера вам не нужно const.

Рассмотрите возможность использования private: членов с методами открытого и закрытого методов получения.

3 голосов
/ 26 ноября 2010

(4) не вариант. Неявно объявленный оператор присваивания копии присваивает каждому члену правостороннего объекта один и тот же элемент левостороннего объекта.

Компилятор не может неявно сгенерировать оператор присваивания копии для класса, который имеет члены с константными данными по той же причине, по которой это недопустимо:

const int i = 1;
i = 2;

(2) проблематично, поскольку вам нужно как-то решить эту проблему.

(1) - очевидное решение; если ваш тип класса имеет константные члены-данные, он не присваивается и присваивание не имеет особого смысла. Почему вы говорите, что это не решение?


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

2 голосов
/ 27 ноября 2010

Я попытаюсь связать ответы вкратце:

Основная проблема заключается в том, что QList требует присутствия оператора присваивания, поскольку они внутренне используют присваивание.Таким образом они смешивают реализацию с интерфейсом.Так что, хотя вам не нужен оператор присваивания, QList не будет работать без него. source

@ 3. Есть std :: List, но он не предлагает постоянный доступ к элементам, в то время как QList делает.

@ 2. Возможносоздав новый объект с конструктором копирования и желаемыми свойствами и вернув его *.Несмотря на то, что вы обходите свойство const, это все же лучше, чем вообще не использовать const, потому что вы позволили бы контейнеру обманывать здесь, но все же не позволяли бы пользователям делать это сами, что было первоначальным намерением сделать этот член константным.

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

@ 1. В конце концов, это кажется самым простым решением.Пока это личное, вы просто должны обратить внимание на то, что объект сам не меняет его.

@ 4. Нет способа заставить его.Он не знает, как, потому что переменная постоянна, и в какой-то момент ему придется сделать this->row = other.row с ранее определенным int const row;.И const означает постоянную даже в этом случае. один источник

@ 5 QList не имеет опций такого типа.

Дополнительные решения:

  • Использование указателя на объекты вместо чистых объектов

* На данный момент не уверен в этом.

...