2010-08-25 1 views
6

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

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

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

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

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

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

+3

Что произойдет, если вы сконфигурируете v только в одной ветке if-then-else? –

+1

Тогда он, вероятно, должен быть 'const' только в этой области. – Frank

+3

Нет. Это не имело бы смысла. –

ответ

19

Честно говоря, я считаю, это меньше запутанным, если переменная либо 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(); 

Обратите внимание на &. Нет смысла копировать результат вызова функции, поскольку привязка rvalue к ссылке const продлевает срок ее службы до конца срока службы ссылки.
Конечно, перекомпиляция с помощью компилятора, который поддерживает семантику перемещения C++ 1x, сделает такие оптимизации практически ненужными. Но привязка rvlaue к ссылке const может все же быть быстрее, чем перемещение вектора и вряд ли будет медленнее.
С C++ 1x вы также можете создавать лямбда-функции, выполняя эту операцию. C++ просто предоставляет невероятно огромный арсенал инструментов. 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); 
} 

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

+0

+1: Я должен согласиться - тот факт, что 'const' не применяется во время выполнения ctor или dtor, уже вызывает достаточную путаницу. –

+0

Я думаю, вы имеете в виду C++ 0x, а не C++ 1x. http://www2.research.att.com/~bs/C++0xFAQ.html – Ben

+0

@Ben: Временные рамки для C++ 0x заканчиваются в этом году, и в этом году не будет нового стандарта. Вероятно, он выйдет в следующем году, что сделает его C++ 11. Если будут дальнейшие задержки, это будет C++ 12. Следовательно, C++ 1x. – sbi

6

Это прекрасное время, чтобы использовать функцию

#include <vector> 

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

int main() 
{ 
    const std::vector<int> myVector = makeVector(); 
} 
+2

Ваше решение имеет другую семантику. OP хочет создать 1 'vector ' и изменить константу, не являющуюся константой идентификатора. Ваше решение создает 2 копии «vector » с двумя различными рычагами доступа – JaredPar

+6

@ Jared: я читал это как: OP хочет по существу создать постоянный вектор, но не может из-за отсутствия списков инициализаторов C++ 0x , или не имея сразу всех данных. – Bill

+8

@ Jared: эта версия, скорее всего, создаст только один вектор (из-за именованной оптимизации значения возвращаемого значения), которую вы можете изменить в одной области видимости, а затем она станет постоянной в другом, точно так же, как OP. – UncleBens

8

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

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

7

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

+2

Это статическая типизация - компилятор может вывести, когда переменная const или не во время компиляции. – liori

+3

@liori: Может быть. Если тип переменной может измениться во время выполнения, это означает, что нам нужно выполнить анализ пути выполнения для статической типизации. Это не будет работать во всех случаях и приведет к неожиданным результатам в других. –

+0

Ах, да, забыл об этом. +1. – liori

3

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

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

3

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

+2

Использование 'const_cast' таким образом приводит к неопределенному поведению. –

+0

Это небезопасно, так как всякий раз, когда вы объявляете переменную как 'const', компилятор может поместить все или часть его в постоянную память, что приведет к ошибкам, если вы отбросите const и попытаетесь изменить объект. Это маловероятно для сложного класса, такого как 'std :: vector', но по-прежнему вызывает беспокойство. –

+0

О, ну. Я сказал, что это небезопасно. –

3

Я предполагаю, что вы говорите о чем-то более общем, чем о инициализации векторов (который решается в 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). Это я могу читать вполне естественно, как: «подготовить вектор const в соответствии с описанной здесь процедурой». Меня беспокоит только количество скобок.

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

(под редакцией после внушения dehmann в)

+1

Хороший ответ. И да, кажется, вам не нужны все круглые скобки в '[]() {...}'. Круглые круглые скобки необязательны, поэтому они становятся '[] {...}' – Frank

0

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

2

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

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

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

2

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

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

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

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

+0

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

+0

+1 Прискорбно, что поздние ответы редко получают достаточное количество оборотов, чтобы подняться на вершину. Некоторые из других ответов хороши, но это может быть лучше всего. – thb