Имеет ли смысл иметь операцию constify в C ++? - PullRequest
6 голосов
/ 25 августа 2010

Имеет ли смысл иметь в C/C++ операцию "constify", которая создает переменную const?

Вот пример, где это может быть полезно, где, очевидно, мы не хотим объявлять его const еще в первой строке:

std::vector<int> v;
v.push_back(5);
constify v; // now it's const

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

std::vector<int> v0;
v0.push_back(5);
const std::vector<int>& v = v0;

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

Ответы [ 10 ]

19 голосов
/ 25 августа 2010

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


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

const std::vector<int> cvi = { 1, 2, 3, 4, 5, 42 }; 

Однако, даже без C ++ 1x 'под рукой, и даже с типами, которые запрещают этот синтаксис инициализации, вы всегда можете создать вспомогательную функцию, чтобы делать то, что вы хотите:

const std::vector<int>& cvi = create_my_vector();

или, если вы хотите быть модным:

const std::vector<int>& cvi = compile_time_list<1,2,3,4,5,42>::create_vector();

Обратите внимание на &. Нет смысла копировать результат вызова функции, поскольку привязка значения r к ссылке const продлевает его время жизни до конца времени жизни ссылки.
Конечно, перекомпиляция с компилятором, который поддерживает семантику перемещения C ++ 1x, сделает такие оптимизации практически ненужными. Но привязка rvlaue к ссылке const может все же быть быстрее, чем перемещение вектора, и вряд ли будет медленнее.
С C ++ 1x вы также можете создавать лямбда-функции, делая это на лету. C ++ просто предоставляет невероятно огромный арсенал инструментов. IME, как бы ты ни думал, кто-то другой должен придумать еще одну идею сделать то же самое. И часто лучше, чем у вас.


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

int main(int argc, char* argv[])
{
  std::istream* istrm = NULL;
  std::ifstream ifs;
  if( argc > 1 )
  {
    ifs.open( argv[1] );
    if( ifs.good() ) 
      istrm = &ifs;
  }
  if( !istrm ) 
    istrm = &std::cin;

  while( istrm->good() )
  {
     // reading from *istrm implemented here
  }
  return 0;
}

просто разделите проблемы на 1) выяснение, откуда читать и 2) фактическое чтение:

int read(std::istream& is)
{
  while( is.good() )
  {
     // reading from is implemented here
  }
  return 0;
}

int main(int argc, char* argv[])
{
  if( argc > 1 )
  {
    std::ifstream ifs( argv[1] );
    if( ifs.good() ) 
      return read(ifs);
  }
  return read(std::cin);
}

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

8 голосов
/ 25 августа 2010

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

C ++ 0x значительно снизит потребность в таком переносеВы сможете использовать фигурные инициализаторы для векторов, чтобы создать / инициализировать вектор за одну операцию.Другие типы будут (по крайней мере потенциально) поддерживать пользовательские инициализаторы, чтобы выполнить примерно то же самое.

7 голосов
/ 25 августа 2010

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

6 голосов
/ 25 августа 2010

Это прекрасное время для использования функции

#include <vector>

std::vector<int> makeVector()
{
  std::vector<int> returnValue;
  returnValue.push_back(5);
  return returnValue;
}

int main()
{
  const std::vector<int> myVector = makeVector();
}
3 голосов
/ 25 августа 2010

Полагаю, вы говорите о чем-то более общем, чем только инициализация векторов (что решается в C ++ 0x), и используете векторы только в качестве примера.

Я бы предпочел, чтобы это было сделано через какие-то локальные функции:

const vector<int> values = []{
    vector<int> v;
    copy(some_other_data.begin(), some_other_data.end(), v);
    sort(v);
    return v;
}();

(я мог бы испортить синтаксис анонимных функций C ++ 0x). Это я могу читать вполне естественно, как: «подготовить вектор констант в соответствии с процедурой, описанной здесь». Меня беспокоит только количество скобок.

Я вижу, как этот код может стать идиомой C ++ после того, как C ++ 0x станет более естественным для программистов.

(отредактировано по предложению Дехмана)

3 голосов
/ 25 августа 2010

Уже упоминалось, что C ++ 0x несколько решает эту проблему с помощью инициализаторов скобок:

const std::vector<int> values{1, 2, 3, 4, 5};

Хотя это допускает только инициализацию и не позволяет, например, вызывать non- constфункции-члены после запуска конструктора. позволяет определить макрос constify следующим образом:

#define constify(type, id) \
for (type const& id##_const(id), & id(id##_const), \
    * constify_index = &id; constify_index; constify_index = 0)

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

std::vector<int> v;

// v is non-const here.

constify (std::vector<int>, v) {

    // v is const here.

}

Это работает путем настройкиfor цикл, который выполняет следующий оператор или блок только один раз, с константной переменной, локальной для тела цикла.Обратите внимание на объявление вспомогательной переменной i_const перед локальной i: оператор int const& i(i) сам инициализирует i в , то есть в неинициализированное значение, и мы хотим, чтобы (i)вместо этого обратитесь к , ранее объявленному i, поэтому требуется дополнительный уровень.

Если вы можете использовать функции C ++ 0x, ключевое слово decltype пригодится,позволяет вам опускать тип из вызовов constify:

#define constify(id) \
for (decltype(id) const& id##_const(id), & id(id##_const), \
    * constify_index = &id; constify_index; constify_index = 0)

, что позволяет вам просто написать:

constify (v) {
    // ...
}

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

3 голосов
/ 25 августа 2010

Я тоже об этом думал.Но, ИМХО, это создаст массу путаницы, которая перевесит ее преимущества.Если подумать, само понятие константности в C ++ уже достаточно запутанно.

Ваша идея сводится к тому, «Как сделать переменную доступной только для чтения после ее инициализации?».Вы можете получить тот же эффект, сделав вашу переменную закрытым членом класса, который инициализируется в конструкторе и для которого вы предоставляете метод получения, но не наборщик.

2 голосов
/ 26 августа 2010

Рассмотрим следующий бит:

void foo(std::vector<int> & v) 
{
  v.push_back(1);
  constify v;
}
void bar() {
  std::vector<int> test(7);
  foo(test);
  test.clear();
}

Согласна ли переменная v в foo?Это та же самая переменная, что и test в баре.Таким образом, вызов test.clear() должен быть недействительным.Я думаю, что вы на самом деле имели в виду, что имя «согласованное», а не переменная.

Было бы на самом деле тривиально указать и реализовать: constify x; - это объявление константной ссылки с именем x, у которой тот же базовый тип, что и у скрытой переменной x.Он следует обычным правилам области видимости, за исключением того, что он может быть определен в той же области видимости, что и предыдущая декларация x.

2 голосов
/ 25 августа 2010

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

Если вы хотите создать оператор constify, вам нужно будет сделать это свойством переменной (без дополнительных ключевых слов, каждой переменной), чтобы она могла изменяться во время выполнения. И, конечно, вам придется генерировать исключение всякий раз, когда программа пытается изменить (в настоящее время) переменную const, что фактически означает, что каждый доступ для записи каждой переменной должен сначала проверять свойство const.

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

0 голосов
/ 25 августа 2010

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

...