Является ли хорошей идеей поддерживать «постоянство» в максимально возможной степени? - PullRequest
10 голосов
/ 16 декабря 2011

Недавно я разработал практику создания многих вещей в своем коде как const:

(1) Аргументы функции , которые, я знаю, никогда не изменится. e.g.:

void foo (const int i, const string s)
          ^^^^^        ^^^^^ 

(2) Типы возврата как const. e.g.:

struct A {
...
  const int foo () { return ...; }
  ^^^^^
  operator const bool () const { return ...; }
           ^^^^^
};

(3) Тривиальные вычисления целых чисел или строк. e.g.:

const uint size = vec.size();
^^^^^
const string s2 = s1 + "hello ";
^^^^^

... и еще несколько мест. Обычно в других реальных кодах я не вижу таких мелкомасштабных переменных, помеченных как const. Но я подумал, что их const никогда не повредит. Это хорошая практика программирования ?

Ответы [ 7 ]

13 голосов
/ 16 декабря 2011

(1) и (3) тесно связаны между собой. Параметр по значению - это просто локальная переменная с таким именем, как результат ваших вычислений.

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

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

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

(2) другое дело. Для встроенных типов это не имеет значения, как объяснили другие. Для типов классов не возвращать по постоянному значению. Это может показаться хорошей идеей, поскольку она не позволяет пользователю писать что-то бессмысленное, например func_returning_a_string() += " extra text";. Но это также препятствует тому, что является точечным - C ++ 11 перемещает семантику. Если foo возвращает константную строку, и я пишу std::string s = "foo"; if (condition) s = foo();, тогда я получаю назначение копирования в s = foo();. Если foo возвращает неконстантную строку, тогда я получаю задание на перемещение.

Аналогично в C ++ 03, который не имеет семантики перемещения, он предотвращает уловку, известную как «swaptimization» - с неконстантным возвращаемым значением я могу написать foo().swap(s); вместо s = foo();.

7 голосов
/ 16 декабря 2011

Да.

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

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

3 голосов
/ 16 декабря 2011

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

То есть следующие два объявления в точности эквивалентны:

int foo();
const int foo();   // this particular const is ignored by the compiler

Если подумать, это имеет смысл: вы все равно не можете написать foo() = 42; без const.

2 голосов
/ 16 декабря 2011

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

void foo (const int i, const string s)

Я был бы настолько смел, чтобы объявить этот прототип неправильным . Если у вас только что был прототип

void foo (int i, string s)

тогда это уже обещание не изменять значения, которые пользователь передает для i и s. (поскольку вы получаете копий этих значений, а не ссылки на оригинал) Таким образом, единственное, что const делает в этом случае, - это просит компилятор пожаловаться, если вы случайно попытались изменить i или s. Но есть и другие способы достижения этого эффекта - на самом деле нет никакого бизнеса, подвергаемого внешним воздействиям.

Теперь, следующее использование const было бы действительно полезно:

void foo (int i, const string &s)

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

Некоторые другие варианты использования хороши:

const size_t size = vec.size();

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

const string s2 = s1 + "hello ";

Это тоже хорошо. Хотя, опять же, я мог бы вместо этого сделать ссылку:

const string &s2 = s1 + "hello ";

Этот

operator const bool () const { return ...; }

наиболее не имеет значения. Вам, вероятно, следует реализовать operator bool вместо operator const bool, даже если по какой-то другой причине, кроме первой, люди ожидают.

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

1 голос
/ 16 декабря 2011

(1) Аргументы функции, которые, я знаю, никогда не будут изменены.

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

void f( int x ) {
  x += 2;
  // OOPS: At this point 'x' is actually not what the caller passed to us!
}

Этого не произойдет, если вы передадите аргумент как const int x. Однако я знаю много кода, в котором программист преднамеренно передает аргумент в качестве значения, но не const, потому что он хочет изменить аргумент - в противном случае он все равно сделает копию. То есть вместо

void f( const std::string &s ) {
    std::string s2 = s;
    s2[0] = 'A';
    // ...
}

они делают

void f( std::string s ) {
    s[0] = 'A';
    // ...
}

Приятным побочным эффектом преднамеренного искажения копии значения, подобного этому, является то, что вам не нужно думать о досадных проблемах с именами (если аргумент равен s_, а очищенное значение - s? Или s2 против s или как?).

Поскольку наличие или отсутствие const здесь вообще не влияет на вызывающего (вы все равно получаете копию значения, поэтому вы не можете повозиться с данными вызывающего), оно просто сводится к следующему. To: Sometiems это имеет смысл. Иногда это не так. Нет хорошего руководства здесь. Мое личное ощущение, что вы не должны беспокоиться, хотя. Если вы хотите сделать аргумент const, чтобы избежать случайного случайного перезаписи *1028*, тогда, возможно, ваша функция слишком длинная (или имя аргумента глупое, например i или * 1031). * или it как-то так).

(2) Типы возвращаемых значений как постоянные.

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

QString f() { return "Hello"; }

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

(3) Тривиальное вычисление целого числа или строк.

Это фактически тот же самый случай, что и (1), который я обсуждал выше. То же самое относится и к делу.

1 голос
/ 16 декабря 2011

Давайте рассмотрим пример:

class Foo {
private:
    int x;
public:
    Foo () : x (0) { }
    Foo (const Foo& other) { x = other.x; }
    void setX(const int newX) {
        x = newX;
    }
    const int getX() const {
        return x;
    }
    const Foo getFoo() const {
        return *this;
    }
};

Заставлять getX () возвращать const int против int глупо. Любой тип возвращаемого значения по POD как const не имеет смысла. Почему это имеет значение? Единственное, что вы можете сделать для неконстантных переменных POD, которые вы не можете сделать для константных, - это присваивание ... и различие lvalue / rvalue позаботится об этом для возвращаемых типов.

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

Foo foo;
/* foo.getFoo().setX(10); */ // ERROR!
// because f.getFoo() returns const Foo

Foo bar;
bar = foo.getFoo();
bar.setX(10); // no error

Создание константного типа параметра POD const защищает от изменений этого параметра внутри функции. Так, например, вы не можете присвоить значение newX внутри setX.

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

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

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

1 голос
/ 16 декабря 2011

Это определенно так, по крайней мере для кода, который должен оставаться поддерживаемым в долгосрочной перспективе.

Если вы хотите больше узнать об этом, в книге «Эффективный C ++» Скотта Мейерса представлены многочисленные сценарии, в которых вам нужны const возвращаемые типы и аргументы методов для защиты вашего кода от неправильного использования.

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