C ++: проблемы STL с постоянными членами класса - PullRequest
13 голосов
/ 30 июля 2010

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

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

РЕДАКТИРОВАТЬ: Как правило, я стремлюсь к правильности, потому что я много многопоточности. Мне редко нужно реализовывать контроль копирования для моих классов и никогда не удалять код (если это не является абсолютно необходимым). Я чувствую, что текущее положение дел с 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_;
};

Ответы [ 14 ]

18 голосов
/ 30 июля 2010

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

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

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

5 голосов
/ 30 июля 2010

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

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

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

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

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

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

    inner *i;
};

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

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

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

5 голосов
/ 30 июля 2010

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

4 голосов
/ 30 июля 2010

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

3 голосов
/ 30 июля 2010

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

Причина

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

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<int &>(this->k) = rhs.k ;
   return *this ;
} ;

Но,затем вы написали в своем вопросе:

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

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

Решение

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

3 голосов
/ 30 июля 2010

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

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

2 голосов
/ 30 июля 2010

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

#include <iostream>

#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/utility.hpp>

#include <vector>

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<const Fruit> FruitPtr;
    typedef std::vector<FruitPtr> FruitVector;
    FruitVector fruits;
    fruits.push_back( boost::make_shared<Fruit>("apple") );
    fruits.push_back( boost::make_shared<Fruit>("banana") );
    fruits.push_back( boost::make_shared<Fruit>("orange") );
    fruits.push_back( boost::make_shared<Fruit>("pear") );
    BOOST_FOREACH( const FruitPtr& fruit, fruits ) {
        fruit->eat();
    }

    return 0;
}

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

1 голос
/ 30 июля 2010

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

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

1 голос
/ 30 июля 2010

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

Лучшее место для использования 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 ++ не позволяет вам этого делать.

0 голосов
/ 30 июля 2010

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

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

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