Would it make sense to have a 'constify' operation in C++?

Would it make sense to have a "constify" operation in C/C++ that makes a variable const?

Here is an example where it could be useful, where obviously we don't want to declare it const yet in the first line:

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

Currently, without such a possibility, you'd have to introduce another variable to get the same effect:

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

That's more confusing since it adds a new name into the scope and you need to make it a reference to avoid copying the whole vector (or use swap?).

6
задан Frank 26 August 2010 в 08:29
поделиться

10 ответов

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


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

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

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

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

или, если хотите пофантазировать:

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

Обратите внимание на &. Нет смысла копировать результат вызова функции, поскольку привязка rvalue к ссылке 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);
}

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

19
ответ дан 8 December 2019 в 02:05
поделиться

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

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

8
ответ дан 8 December 2019 в 02:05
поделиться

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

7
ответ дан 8 December 2019 в 02:05
поделиться

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

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

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

2
ответ дан 8 December 2019 в 02:05
поделиться

Я предполагаю, что вы говорите о чем-то более общем, чем просто инициализация векторов (которая решена в 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
ответ дан 8 December 2019 в 02:05
поделиться

Сейчас самое время использовать функцию

#include <vector>

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

int main()
{
  const std::vector<int> myVector = makeVector();
}
6
ответ дан 8 December 2019 в 02:05
поделиться

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

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
ответ дан 8 December 2019 в 02:05
поделиться

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

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

3
ответ дан 8 December 2019 в 02:05
поделиться

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

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

Хотя это позволяет только инициализацию и не позволяет, например, вызывать не-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
ответ дан 8 December 2019 в 02:05
поделиться

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

.
0
ответ дан 8 December 2019 в 02:05
поделиться
Другие вопросы по тегам:

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