C++: STL беспокоится с участниками класса константы

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

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

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

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

class Multiply {
public:
    Multiply(double coef) : coef_(coef) {}
    double operator()(double x) const {
        return coef_*x;
    }
private:
    const double coef_;
};
14
задан user401947 30 July 2010 в 15:45
поделиться

14 ответов

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

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

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

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

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

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

    inner *i;
};

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

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

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

5
ответ дан 1 December 2019 в 06:53
поделиться

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

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

0
ответ дан 1 December 2019 в 06:53
поделиться

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

4
ответ дан 1 December 2019 в 06:53
поделиться

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

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

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

18
ответ дан 1 December 2019 в 06:53
поделиться

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

5
ответ дан 1 December 2019 в 06:53
поделиться

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

Причина

I у вас есть что-то вроде:

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 изменилось в течение времени жизни объекта, это означает, что вы противоречите себе, потому что вам нужна переменная-член, которая не изменяется в течение времени существования объекта , если вы не хотите, чтобы она изменялась !

Решение

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

3
ответ дан 1 December 2019 в 06:53
поделиться

вы можете сохранить 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;
}

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

2
ответ дан 1 December 2019 в 06:53
поделиться

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

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

Пример временной вспомогательной переменной:

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

1
ответ дан 1 December 2019 в 06:53
поделиться

Я думаю, что ваше утверждение

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

Возможно, неверно. У меня есть классы с методом const

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

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

0
ответ дан 1 December 2019 в 06:53
поделиться

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

Обновление
Существует некоторое недопонимание того, что означает 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<CTheta> v;
    const size_t nMax(12);

    for (size_t i=0; i<nMax; i++)
    {
        v.push_back(CTheta(::rand()));
    }
    std::sort(v.begin(), v.end());
    std::vector<CTheta>::const_iterator itr;
    for (itr=v.begin(); itr!=v.end(); ++itr)
    {
        std::cout << itr->GetVal() << " " << itr->GetPi() << std::endl;
    }
    return 0;
}
0
ответ дан 1 December 2019 в 06:53
поделиться

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

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

-1
ответ дан 1 December 2019 в 06:53
поделиться

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

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

3
ответ дан 1 December 2019 в 06:53
поделиться

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

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

1
ответ дан 1 December 2019 в 06:53
поделиться

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

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

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

#include <new>
#include <iostream>
struct Foo {
    Foo(int x):x(x){}
    const int x;
    friend std::ostream & operator << (std::ostream & os, Foo const & f ){
         os << f.x;
         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;

}

output

1
2
1
1

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

0
ответ дан 1 December 2019 в 06:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: