Intereting Posts

C ++: проблемы с STL с членами classа const

Это открытый вопрос. Эффективный C ++. Пункт 3. Используйте константу, когда это возможно. В самом деле?

Я хотел бы сделать все, что не меняется во время жизни const. Но состретется со своими собственными проблемами. Если class имеет какой-либо константный член, оператор сгенерированного оператора компилятора отключается. Без оператора присваивания class не будет работать с STL. Если вы хотите предоставить свой собственный оператор назначения, необходим const_cast . Это означает больше шума и больше места для ошибок. Как часто вы используете члены classа const?

EDIT: Как правило, я стремлюсь к константной корректности, потому что много многопоточности. Мне редко приходится реализовывать контроль копирования для моих classов и никогда не удалять код (если это абсолютно необходимо). Я чувствую, что текущее состояние дел с const противоречит моему стилю кодирования. Const заставляет меня реализовать оператор присваивания, хотя он мне не нужен. Даже без назначения const_cast это хлопот. Вы должны убедиться, что все члены константы сравниваются с равными, а затем вручную копируют все неконстантные члены.

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

class Multiply { public: Multiply(double coef) : coef_(coef) {} double operator()(double x) const { return coef_*x; } private: const double coef_; }; 

Как отметил АндрейТ, в этих условиях назначение (в основном) не имеет большого смысла. Проблема в том, что vector (для одного примера) является скорее исключением из этого правила.

Логически, вы копируете объект в vector , а через некоторое время вы возвращаете другую копию исходного объекта. С чисто логической точки зрения, никакого задания не требуется. Проблема состоит в том, что vector требует, чтобы объект был назначен в любом случае (фактически, все контейнеры на C ++). Это в основном делает деталь реализации (что где-то в коде, он может назначать объекты, а не копировать их) часть интерфейса.

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

C ++ 11 и новее добавьте несколько новых поворотов в эту ситуацию. В частности, объекты больше не должны быть назначены для хранения в векторе (или других коллекциях). Достаточно, чтобы они были подвижными. Это не помогает в этом конкретном случае (перемещать объект const не проще, чем назначать его), но в некоторых других случаях значительно облегчает жизнь (т. Е. Существуют, конечно, типы, которые могут быть перемещаемыми, но не назначаемыми / копируемыми ).

В этом случае вы можете использовать перемещение, а не копию, добавляя уровень косвенности. Если вы создаете «внешний» и «внутренний» объект, с элементом const во внутреннем объекте, а внешний объект просто содержит указатель на внутренний:

 struct outer { struct inner { const double coeff; }; inner *i; }; 

… тогда, когда мы создаем экземпляр outer , мы определяем inner объект для хранения данных const . Когда нам нужно выполнить задание, мы выполняем типичное назначение переноса: скопируйте указатель со старого объекта на новый и (возможно) установите указатель в старом объекте на nullptr, поэтому, когда он будет уничтожен, Попробуйте уничтожить внутренний объект.

Если вы хотите достаточно плохо, вы можете использовать (вроде) тот же метод в более старых версиях C ++. Вы все равно будете использовать внешние / внутренние classы, но каждое присваивание будет выделять целый новый внутренний объект, или вы будете использовать что-то вроде shared_ptr, чтобы внешние экземпляры могли обмениваться доступом к одному внутреннему объекту и очищать его, когда последний внешний объект уничтожается.

Это не имеет никакого реального значения, но, по крайней мере, для назначения, используемого при управлении вектором, у вас будет только две ссылки на inner то время как vector меняет размер (изменение размера – это то, почему вектор требует назначения для начала).

Вы сами сказали, что вы создаете const «все, что не меняется во время жизни объектов». Тем не менее вы жалуетесь, что оператор неявно объявленного присваивания отключается. Но неявно объявленный оператор присваивания меняет содержимое члена, о котором идет речь! Совершенно логично (по вашей собственной логике), что он отключается. Либо это, либо вы не должны объявлять этот член const.

Кроме того, предоставление вам собственного оператора присваивания не требует const_cast . Зачем? Вы пытаетесь присвоить члену, который вы объявили const внутри вашего оператора присваивания? Если да, то почему вы объявили его const?

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

Я очень редко их использую – хлопот слишком велик. Конечно, я всегда стремлюсь к константной корректности, когда речь идет о функциях члена, параметрах или типах возврата.

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

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

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

Мне интересно о вашем случае … Все ниже – это предположение, потому что вы не представили пример кода, описывающего вашу проблему, поэтому …

Причина

Я думаю, у вас есть что-то вроде:

 struct MyValue { int i ; const int k ; } ; 

IIRC, оператор присваивания по умолчанию будет выполнять назначение по отдельности, что сродни:

 MyValue & operator = (const MyValue & rhs) { this->i = rhs.i ; this->k = rhs.k ; // THIS WON'T WORK BECAUSE K IS CONST return *this ; } ; 

Таким образом, это не будет генерироваться.

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

Насколько я вижу это:

  1. Компилятор прав, чтобы не генерировать этот operator =
  2. Вы должны предоставить свои собственные, потому что только вы точно знаете, что хотите

Ваше решение

Я боюсь понять, что вы подразумеваете под const_cast .

Мое собственное решение вашей проблемы состояло в том, чтобы написать следующий пользовательский оператор:

 MyValue & operator = (const MyValue & rhs) { this->i = rhs.i ; // DON'T COPY K. K IS CONST, SO IT SHOULD NO BE MODIFIED. return *this ; } ; 

Таким образом, если у вас будет:

 MyValue a = { 1, 2 }, b = {10, 20} ; a = b ; // a is now { 10, 2 } 

Насколько я вижу, он последователен. Но, я думаю, читая решение const_cast , вы хотите иметь что-то большее:

 MyValue a = { 1, 2 }, b = {10, 20} ; a = b ; // a is now { 10, 20 } : K WAS COPIED 

Это означает следующий код для operator = :

 MyValue & operator = (const MyValue & rhs) { this->i = rhs.i ; const_cast(this->k) = rhs.k ; return *this ; } ; 

Но тогда вы написали в своем вопросе:

Я хотел бы сделать все, что не меняется во время жизни объекта const

С тем, что я предполагал, это ваше собственное решение const_cast , k изменилось во время жизни объекта, что означает, что вы противоречите себе, потому что вам нужна переменная-член, которая не изменяется во время жизни объекта, если вы не хотите, чтобы она изменилась !

Решение

Примите тот факт, что ваша переменная-член изменится в течение всего срока жизни объекта-владельца и удалит константу.

вы можете хранить shared_ptr в своих объектах const в контейнерах STL, если вы хотите сохранить const члены.

 #include  #include  #include  #include  #include  #include  class Fruit : boost::noncopyable { public: Fruit( const std::string& name ) : _name( name ) { } void eat() const { std::cout << "eating " << _name << std::endl; } private: const std::string _name; }; int main() { typedef boost::shared_ptr FruitPtr; typedef std::vector FruitVector; FruitVector fruits; fruits.push_back( boost::make_shared("apple") ); fruits.push_back( boost::make_shared("banana") ); fruits.push_back( boost::make_shared("orange") ); fruits.push_back( boost::make_shared("pear") ); BOOST_FOREACH( const FruitPtr& fruit, fruits ) { fruit->eat(); } return 0; } 

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

Я использую только константы в ссылочных или указательных classах. Я использую его, чтобы указать, что цель ссылки или указатель не должна изменяться. Использование его на других classах участников – это большая проблема, как вы узнали.

Лучшие места для использования const – это параметры функции, указатели и ссылки всех видов, постоянные целые числа и временные удобства.

Примером временной переменной удобства будет:

 char buf[256]; char * const buf_end = buf + sizeof(buf); fill_buf(buf, buf_end); const size_t len = strlen(buf); 

Этот указатель buf_end никогда не должен указывать где-либо еще, поэтому создание его является хорошей идеей. Та же идея с len . Если строка внутри buf никогда не изменяется в остальной части функции, то ее len не должен изменяться. Если бы я мог, я бы даже изменил buf на const после вызова fill_buf , но C / C ++ не позволяет вам это делать.

Дело в том, что плакат нуждается в защите const в своей реализации, но все же хочет, чтобы объект был назначен. Язык не поддерживает такую ​​семантику удобно, поскольку константа элемента находится на одном и том же логическом уровне и тесно связана с назначаемостью.

Тем не менее, идиома pImpl со ссылкой на подсчитанную реализацию или умный указатель будет делать именно то, что хочет плакат, поскольку назначаемость затем переносится из реализации и выше уровня на объект более высокого уровня. Объект реализации строится / разрушается только тогда, когда назначение никогда не требуется на более низком уровне.

Я думаю, ваше заявление

Если class имеет const любой член, сгенерированный оператор присваивания компилятора отключается.

Возможно, это неверно. У меня есть classы, которые имеют метод const

 bool is_error(void) const; .... virtual std::string info(void) const; .... 

которые также используются с STL. Итак, возможно, ваше наблюдение зависит от компилятора или применимо только к переменным-членам?

Я бы использовал только член const, если сам class не копируется. У меня много classов, которые я объявляю с boost :: noncopyable

 class Foo : public boost::noncopyable { const int x; const int y; } 

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

 #include  #include  struct Foo { Foo(int x):x(x){} const int x; friend std::ostream & operator << (std::ostream & os, Foo const & f ){ os << fx; return os; } }; int main(int, char * a[]){ Foo foo(1); Foo bar(2); std::cout << foo << std::endl; std::cout << bar<< std::endl; new(&bar)Foo(foo); std::cout << foo << std::endl; std::cout << bar << std::endl; } 

выходы

 1 2 1 1 

foo был скопирован в бар с использованием оператора нового места размещения.

Это не слишком сложно. У вас не должно возникнуть проблем с созданием собственного оператора присваивания. Константные биты не нужно назначать (поскольку они являются константами).

Обновить
Существует некоторое недоразумение в отношении того, что означает const. Это означает, что он никогда не изменится.

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

 class CTheta { public: CTheta(int nVal) : m_nVal(nVal), m_pi(3.142) { } double GetPi() const { return m_pi; } int GetVal() const { return m_nVal; } CTheta &operator =(const CTheta &x) { if (this != &x) { m_nVal = x.GetVal(); } return *this; } private: int m_nVal; const double m_pi; }; bool operator < (const CTheta &lhs, const CTheta &rhs) { return lhs.GetVal() < rhs.GetVal(); } int main() { std::vector v; const size_t nMax(12); for (size_t i=0; i::const_iterator itr; for (itr=v.begin(); itr!=v.end(); ++itr) { std::cout << itr->GetVal() << " " << itr->GetPi() << std::endl; } return 0; } 

Философски говоря, это выглядит как компромисс в области безопасности. Const используется для безопасности. Как я понимаю, контейнеры используют назначение для повторного использования памяти, то есть ради производительности. Вместо этого они могут использовать явное уничтожение и размещение новых (и логично, что это более правильно), но назначение имеет шанс быть более эффективным. Я полагаю, это логически избыточное требование «быть назначаемым» (достаточно для создания копии), но контейнеры stl хотят быть быстрее и проще.

Конечно, можно реализовать назначение как явное уничтожение + размещение нового, чтобы избежать взлома const_cast

Вы в принципе никогда не хотите вставлять переменную-член const в class. (То же самое с использованием ссылок в качестве членов classа.)

Константа действительно предназначена для streamа управления вашей программой – для предотвращения мутации объектов в неправильные моменты времени в вашем коде. Поэтому не объявляйте переменные const member в определении вашего classа, а делайте все это или ничего, когда вы объявляете экземпляры classа.