Oberon space
General Category => Общий раздел => Тема начата: Berserker от Март 14, 2013, 04:55:01 pm
-
Подскажите, пожалуйста, если есть знатоки.
Обыскал весь инет, везде твердится одно и то же: ссылки инициализируются единожды. Повторная установка невозможна.
Как тогда GNU C++ компилирует это:
for (size_t i = 0; i < scheta_.size(); i++)
{
const Schyot &schyot = scheta_; // На каждой итерации разный объект
..
}
-
Подскажите, пожалуйста, если есть знатоки.
Обыскал весь инет, везде твердится одно и то же: ссылки инициализируются единожды. Повторная установка невозможна.
Как тогда GNU C++ компилирует это:
for (size_t i = 0; i < scheta_.size(); i++)
{
const Schyot &schyot = scheta_; // На каждой итерации разный объект
..
}
А какие проблемы? Время жизни переменной - блок. При каждой итерации "блок" создается заново. То есть все переменные что объявлены в блоке создаются заново (понятно что там могут быть оптимизации, но семантически это так).
То же самое что и в случае функции:
void foo(int i) {
const Bar b& = some_bar[i];
std::cout << b << std::endl;
}
void boo() {
for (size_t i=0; i<100500; i++) foo(i);
}
Разницы никакой (более того, и код будет сгенерирован наверняка идентичный).
PS. А что такое GNU C++? Вроде бы обычный стандартный C++98 в данном коде.
-
На всякий случай поясню - объявление переменной по месту использования в С++ это НЕ синтаксический сахар над VAR-блоком Оберона, тут реально другая семантика, и в том же Обероне подобно сэмулировать вменяемым образом невозможно. Соответственно нельзя нормально понять семантику С++ не выйдя за границы семантики Оберона.
-
Спасибо. Именно то, что я искал. Значит время жизни переменной в теле цикла — одна итерация. Сбил меня ответ по C#:
Most of the time, it does not matter whether you declare a variable inside or outside the loop; the rules of definite assignment ensure that it doesn't matter.
По поводу отличий от Оберона, так я уже и так освоился. В том числе с существенной частью особенностей последнего стандарта.
GNU C++ Compiler имелся в виду.
-
Спасибо. Именно то, что я искал. Значит время жизни переменной в теле цикла — одна итерация. Сбил меня ответ по C#:
Most of the time, it does not matter whether you declare a variable inside or outside the loop; the rules of definite assignment ensure that it doesn't matter.
А при чем тут шарп? Это вообще другой язык, к плюсам не имеющий никакого отношения. Там ссылок аналогичных плюсам просто нет.
С тем же успехом можно было читать ответ по Аде или там хаскелю :-)
-
Кстати, а для чего и зачем C++ используешь?
-
Ссылки — это автоматически разыменовываемые указатели. Практически полный аналог (*obj). И реализуются они в 99.99% (если не 100%) через указатели.
Почему ссылки запретили явно переустанавливать не ясно, в итоге функциональность пострадала, а плюсов не видно.
При желании можно изменить ссылку вполне легально.
string s[3];
s[0] = "1";
s[1] = "2";
s[2] = "3";
int i = 0;
reseat_reference: const string &ref = s[i];
kon << ref;
i++;
if (i <= 2)
{
goto reseat_reference;
}
Результат: 123 в консоли.
Кстати, а для чего и зачем C++ используешь?
Первый раз в образовательных целях. Простейший сервер, выдающий по прямому запросу текущее изображение рабочего стола в jpg-формате. Второй раз были лабораторные студентам.
Сейчас начал переписывать модуль с Делфи на С++, виртуализирующий набор WinAPI для работы с файловой системой. В результате можно подключать любые папки в качестве корневого каталога программы для реализации системы модов. Ещё не дописал.
Сейчас нужно сделать эмулятор работы терминала банка и самого банка для студентов в рамках изучения дисциплины «Проектирование информационных систем» и генерации кода из UML-диаграм в Rational Rose 2003.
-
Ссылки — это автоматически разыменовываемые указатели. Практически полный аналог (*obj). И реализуются они в 99.99% (если не 100%) через указатели.
Почему ссылки запретили явно переустанавливать не ясно, в итоге функциональность пострадала, а плюсов не видно.
При желании можно изменить ссылку вполне легально.
string s[3];
s[0] = "1";
s[1] = "2";
s[2] = "3";
int i = 0;
reseat_reference: const string &ref = s[i];
kon << ref;
i++;
if (i <= 2)
{
goto reseat_reference;
}
Результат: 123 в консоли.
1) это не изменение ссылки.
2) ссылка это алиас а не аналог указателя.
Перепиши, пожалуйста, вот этот код на указателях:
struct O {int foo;};
O foo() {
O o = {42};
return o;
}
int main() {
const O& o = foo();
cout << o.foo;
return 0;
}
Результат конечно должен быть чистым, то есть чтобы при любых флагах компилятора не было варнингов.
Сейчас начал переписывать модуль с Делфи на С++, виртуализирующий набор WinAPI для работы с файловой системой. В результате можно подключать любые папки в качестве корневого каталога программы для реализации системы модов. Ещё не дописал.
Сейчас нужно сделать эмулятор работы терминала банка и самого банка для студентов в рамках изучения дисциплины «Проектирование информационных систем» и генерации кода из UML-диаграм в Rational Rose 2003.
Прикольно.
-
1) это не изменение ссылки.
А что тогда? Ссылка (на физической уровне указатель) сперва содержит адрес s[0], потом s[1], потом s[2].
struct O {int foo;};
O foo() {
O o = {42};
return o;
}
int main() {
const O& o = foo();
cout << o.foo;
return 0;
}
Я не профессионал, поправьте, если ошибусь.
const O o = foo();
cout << o.foo;
return 0;
Фактически в Вашем коде:
O temp;
foo(&temp);
const O& o = temp;
У меня:
O temp;
foo(&temp);
// Здесь могло бы быть бессмысленное копирование временной структуры в такую же временную
const O& o = temp;
-
Ссылки — это автоматически разыменовываемые указатели. Практически полный аналог (*obj). И реализуются они в 99.99% (если не 100%) через указатели.
Почему ссылки запретили явно переустанавливать не ясно, в итоге функциональность пострадала, а плюсов не видно.
1. Запретили переустанавливать потому что ссылка должна себя вести как объект. В частности, "o1 = o2" должно копировать объект. Т.е., для переустановки ссылки должен быть отдельный синтаксис. Кроме того, подобная "жесткость" позволяет проще читать код - примерно как запрет break в циклах в некоторых языках ;) Если нужно что-то менять по ходу - всегда есть указатели.
2. Плюсы:
- отсутствие звездочек и стрелочек (самый очевидный)
- перегрузка операторов (ради нее все и затевалось, насколько я помню). Т.е., "p1 + p2" перегрузить нельзя.
- Можно использовать соглашение, по которому ссылка - это ненулевой указатель. Очень удобно, потому что убирает кучу проверок указателя на ноль (код чище) - ссылка никогда не может быть нулевой. И без жутких шаблонов. Единственный недостаток - в отличие от нормального ненулевого указателя ссылку нельзя переинитить, да (но на этот случай можно обратиться к жутким шаблонам).
-
Но согласитесь, Влад, не так уж сложно было ввести нечто вроде:
set TObj &ref = another_obj;
Цена-то — одно ключевое слово и минимальные изменения в компиляторе.
-
Но согласитесь, Влад, не так уж сложно было ввести нечто вроде:
set TObj &ref = another_obj;
Цена-то — одно ключевое слово и минимальные изменения в компиляторе.
Проблема ИМХО в другом.. Алексей богомерзким примером засандаливает ссылку на локальную переменную определенную в функции foo() - лично я до такого не опускался... и не уверен, что это гуд... и даже не знал, что такое возможно... :), век живи век учись... однако.
-
Но согласитесь, Влад, не так уж сложно было ввести нечто вроде:
set TObj &ref = another_obj;
Цена-то — одно ключевое слово и минимальные изменения в компиляторе.
Проблема ИМХО в другом.. Алексей богомерзким примером засандаливает ссылку на локальную переменную определенную в функции foo() - лично я до такого не опускался... и не уверен, что это гуд... и даже не знал, что такое возможно... :), век живи век учись... однако.
Во-первых это не локальная переменная, а возвращаемое значение (то что было в моем примере).
Во-вторых это да, полностью безопасно и полностью легально в языке (этот кейс явно прописан в стандарте).
-
Но согласитесь, Влад, не так уж сложно было ввести нечто вроде:
set TObj &ref = another_obj;
Цена-то — одно ключевое слово и минимальные изменения в компиляторе.
Цена будет огромна.
Весь смысл ссылок в том, что их нет.
То есть ссылка не существует как отдельная сущность, у ссылки нет адреса. А у указателя адрес есть (и по этому адресу лежит адрес того на что он указывает). Поэтому невозможно поставить эксперимент который показал бы отличие создания новой ссылки (ака алиаса) от модификации имеющейся.
Нечто подобное есть и в обероне - это VAR параметры. Они по сути являются неким аналогом плюсатых ссылок. Попробуй ка сделать так, чтобы этот параметр начал ссылаться на другую переменную. Можно еще повозмущаться: почему вирт не предусмотрел простую конструкцию:
SET ИмяВарПараметра = another_object;
Ведь в компиляторе всего пару строк изменить :-)
-
Во-первых это не локальная переменная, а возвращаемое значение (то что было в моем примере).
Во-вторых это да, полностью безопасно и полностью легально в языке (этот кейс явно прописан в стандарте).
Не совсем так... значение это есть ЗНАЧЕНИЕ.. ,адрес ПЕРЕМЕННОЙ есть адрес... я говорю про то, что вы не можете же
сделать int & b=456; ... а тут аналогично присваивается ЧТО? впрочем если этот случай рассматривается отдельно.. почему бы и нет..., только стройности языку это не прибавляет...
-
Во-первых это не локальная переменная, а возвращаемое значение (то что было в моем примере).
Во-вторых это да, полностью безопасно и полностью легально в языке (этот кейс явно прописан в стандарте).
Не совсем так... значение это есть ЗНАЧЕНИЕ.. ,адрес ПЕРЕМЕННОЙ есть адрес... я говорю про то, что вы не можете же
сделать int & b=456; ... а тут аналогично присваивается ЧТО? впрочем если этот случай рассматривается отдельно.. почему бы и нет..., только стройности языку это не прибавляет...
int &b = 456 я сделать конечно не могу, а вот
const int& b = 456 сделать я безусловно могу, это абсолютно валидная операция.
-
const int& b = 456 сделать я безусловно могу, это абсолютно валидная операция.
и... что она делает?
-
const int& b = 456 сделать я безусловно могу, это абсолютно валидная операция.
и... что она делает?
Как обычно, создает алиас. Ссылки больше ничего и не делают в общем то, это тупо алиасы.
-
const int& b = 456 сделать я безусловно могу, это абсолютно валидная операция.
и... что она делает?
Как обычно, создает алиас. Ссылки больше ничего и не делают в общем то, это тупо алиасы.
на что?
-
const int& b = 456 сделать я безусловно могу, это абсолютно валидная операция.
и... что она делает?
Как обычно, создает алиас. Ссылки больше ничего и не делают в общем то, это тупо алиасы.
на что?
На 456, очевидно. Какими именно механизмами компилятор будет обеспечивать аиасность этого алиаса - дело десятое. В зависимости от ситуации он может использовать то одно, то другое, то комбинацию.
Так что нет, ссылка в плюсах это никак не указатель. У ссылки совершенно другая семантика.
-
const int& b = 456 сделать я безусловно могу, это абсолютно валидная операция.
и... что она делает?
Как обычно, создает алиас. Ссылки больше ничего и не делают в общем то, это тупо алиасы.
на что?
На 456, очевидно. Какими именно механизмами компилятор будет обеспечивать аиасность этого алиаса - дело десятое. В зависимости от ситуации он может использовать то одно, то другое, то комбинацию.
Так что нет, ссылка в плюсах это никак не указатель. У ссылки совершенно другая семантика.
а причем здесь семантика... ну хорошо , а в чем отличие от const int b=456 - разве здесь b не является "алиясом" числа 456?
-
а причем здесь семантика... ну хорошо , а в чем отличие от const int b=456 - разве здесь b не является "алиясом" числа 456?
А там начинаются тонкие различия. Ну, например const_cast похоже работает над ними по разному.
void foo(int* p) {*p=12;}
int main() {
const int& a = 42;
const int b = 42;
foo(const_cast<int*>(&a));
foo(const_cast<int*>(&b));
cout << a << " " << b << endl;
return 0;
}
На gcc имеем два разных числа. На clang имеем два одинаковых числа :-D
Одно из двух - либо тут мы имеем UB, либо gcc глючит.
$ g++ --version
g++ (Debian 4.4.5-8) 4.4.5
-
Ду, ну и const int b можно влепить volatile и под него будет гарантированно выделена память (и вполне вероятно, оно и так будет выделено), а вот со ссылкой такой фокус не пройдет.
-
alias - это синоним.
На практике этот синоним поддерживает операцию получения адреса объекта, а значит хранит где-то этот адрес, за исключением оптимизаций. Так вот ячейка памяти, хранящая чей-то адрес, как была указателем, так и остаётся.
Что касаемо оптимизаций, то они просты. Если ссылка на деле указывает на объект в стеке/глобальный объект или как в примере с 456 ни на что не указывает, то нет нужды хранить указатель в памяти и выполнять операцию разыменования. Однако я уже привёл пример с goto, когда реальное значение ссылки меняется. Следовательно, компилятор либо всегда безопасно реализует ссылки через указатели, либо анализирует поток управления.
Ещё раз повторю. Ссылка не синоним. Ведь синоним можно было бы заменить оригинальным объектом во всех местах упоминания. Скажем, если ref является синонимом s[0], то упоминание ref можно было бы заменить на s[0], но это не так по двум причинам:
1) s[0] может скрыто вести к вызову метода, а значит это выражение динамическое. Единственный остающийся вариант всегда указывать на то, что когда-то было s[0] — это хранить указатель или адрес памяти.
2) Пример с goto показывает, что поменять адрес у ссылки можно. После этого она и логически (s[1], s[2]) и физически (&s[1], &s[2]) указывает на новый объект.
Тут у меня вопросов к Defective C++ FQA Lite нет.
P.S. Я ничего не критикую, просто мистика и чёрная магия в описаниях к реальной практике отношения имеют мало.
-
alias - это синоним.
нет alias - это ПСЕВДОНИМ (т.е. ДРУГОЕ НАЗВАНИЕ ОДНОГО И ТОГО ЖЕ ОБЬЕКТА -так переводится)
-
Да, я оговорился.
-
Если ссылка на деле указывает на объект в стеке/глобальный объект или как в примере с 456 ни на что не указывает, то нет нужды хранить указатель в памяти и выполнять операцию разыменования.
пример, с 456 можно трактовать - как сохранение адреса области памяти в которой хранится значение 456 - и если оптимизация отключена - то эта константа ДОЛЖНА быть сохранена в какой то ячейке памяти.. кстати, этот фокус (как напомнил Алексей ), можно проделать только используя модификатор const, что в принципе говорит об "особом" варианте интерпретации...
-
Насколько я помню, модификация константного объекта - это UB. Т.е. const_cast валиден только для объекта, который не константен в оригинале (а, например, передан по константной ссылке).
-
Насколько я помню, модификация константного объекта - это UB.
Но вот такой код обязан работать везде и с любыми оптимизациями:
void f(int const& n){const_cast<int&>(n) = 456;}
int n = 123;
f(n);
assert(n == 456);
-
void f(int const& n){const_cast<int&>(n) = 456;}
int n = 123;
f(n);
assert(n == 456);
это понятно и потому и не интересно.. лучше ответьте на следующий вопрос -в чем прелесть
const int t& b = rvalue; в сравнении с const int t b = rvalue;? (именно для этого специального случая - когда rvalue ЗНАЧЕНИЕ соответствующего типа ;) этот вопрос в том числе и к Алексею , коль скоро он такую хренотень привел... то есть перефразирую вопрос.. А на хрена нам нужно эмулировать первое выражение с помощью указателей? )
-
сорри, одинокая t - в выражениях случайный артефакт...
-
А на хрена нам нужно эмулировать первое выражение с помощью указателей? )
Лично я вижу пока 2 пункта:
1. Вы... нуться
2. Отгрести потенциальный геморрой в будущем..
:D
-
в чем прелесть
const int t& b = rvalue; в сравнении с const int t b = rvalue;? (именно для этого специального случая - когда rvalue ЗНАЧЕНИЕ соответствующего типа ;)
В том, что нет копирования в сучае, если rvalue - тоже ссылка (конкретно для интов, конечно, не акутально)?
X const& f();
X const& x1 = f(); // нет копирования
X const x2 = f(); // есть копирование
-
в чем прелесть
const int t& b = rvalue; в сравнении с const int t b = rvalue;? (именно для этого специального случая - когда rvalue ЗНАЧЕНИЕ соответствующего типа ;)
В том, что нет копирования в сучае, если rvalue - тоже ссылка (конкретно для интов, конечно, не акутально)?
X const& f();
X const& x1 = f(); // нет копирования
X const x2 = f(); // есть копирование
;D Влад - не юлите.. я спрашивал вас ровно про тот случай который просил эмулировать Алексей - а не ту очевидную хрень , на которую вы пытаетесь заострить внимание..
-
в чем прелесть
const int t& b = rvalue; в сравнении с const int t b = rvalue;? (именно для этого специального случая - когда rvalue ЗНАЧЕНИЕ соответствующего типа ;)
В том, что нет копирования в сучае, если rvalue - тоже ссылка (конкретно для интов, конечно, не акутально)?
X const& f();
X const& x1 = f(); // нет копирования
X const x2 = f(); // есть копирование
;D Влад - не юлите.. я спрашивал вас ровно про тот случай который просил эмулировать Алексей - а не ту очевидную хрень , на которую вы пытаетесь заострить внимание..
Чтобы было понятней Владу и все остальным, вот модифицированный текст того примера. Нужно показать пальцем, то есть поставить эксперимент который выявит различие семантики для "const O&" и "const O":
#include <iostream>
using namespace std;
struct O {
int foo;
O(int i) : foo(i) {cout << "O(" << i <<")\n";}
~O(){cout << "~O(" << foo << ")\n";}
};
int i=0;
O foo() {
O o(i++);
return o;
}
int main() {
{const O& o0 = foo();}
{const O o1 = foo();}
return 0;
}
-
Чтобы было понятней Владу и все остальным, вот модифицированный текст того примера. Нужно показать пальцем, то есть поставить эксперимент который выявит различие семантики для "const O&" и "const O":
#include <iostream>
using namespace std;
struct O {
int foo;
O(int i) : foo(i) {cout << "O(" << i <<")\n";}
~O(){cout << "~O(" << foo << ")\n";}
};
int i=0;
O foo() {
O o(i++);
return o;
}
int main() {
{const O& o0 = foo();}
{const O o1 = foo();}
return 0;
}
Да, естественно тип O можно менять как угодно. Ну и в main'e можно что-нибудь дописывать.
-
А буквы O и o были выбраны чтобы понятней было?
"o0" - это, конечно, сильно.
Сильно усугубляет блевотность.
-
А буквы O и o были выбраны чтобы понятней было?
"o0" - это, конечно, сильно.
Сильно усугубляет блевотность.
ну да.. не без вы.. ой программерской эстетики :D , но если не нравится - используйте вместо структуры обычный встроенный тип - например int, соответственно и функция должна возвращать значение этого же типа.
-
А буквы O и o были выбраны чтобы понятней было?
"o0" - это, конечно, сильно.
Сильно усугубляет блевотность.
ну да.. не без вы.. ой программерской эстетики :D , но если не нравится - используйте вместо структуры обычный встроенный тип - например int, соответственно и функция должна возвращать значение этого же типа.
Нет, нельзя.
-
Нет, нельзя.
Ну.. ладно оО, так оО
-
Не оО, а o0.
8)
-
а лучше всё же о_О
-
Чтобы было понятней Владу и все остальным, вот модифицированный текст того примера. Нужно показать пальцем, то есть поставить эксперимент который выявит различие семантики для "const O&" и "const O":
Могу предположить, что в случае "const O" будет UB при попытке последующего const_cast. Но на практике разницы не будет (сомневаюсь, что какой-то компилятор будет класть такую локальную переменную в RO-память). На практике скорее найдется компилятор, который сделает лишнее копирование для случая "const O" (помню борланд чем-то таким похожим страдал).
-
Чтобы было понятней Владу и все остальным, вот модифицированный текст того примера. Нужно показать пальцем, то есть поставить эксперимент который выявит различие семантики для "const O&" и "const O":
Могу предположить, что в случае "const O" будет UB при попытке последующего const_cast. Но на практике разницы не будет (сомневаюсь, что какой-то компилятор будет класть такую локальную переменную в RO-память). На практике скорее найдется компилятор, который сделает лишнее копирование для случая "const O" (помню борланд чем-то таким похожим страдал).
Я проверял на gcc и clang - нигде лишних копирований нет в данном коде. На самом деле я подозреваю, что может быть разница если функцию foo засунуть в отдельную динамическую либу (aka .so) -- в этом случае у компилятора меньше возможностей суровой оптимизации.
-
Я проверял на gcc и clang - нигде лишних копирований нет в данном коде. На самом деле я подозреваю, что может быть разница если функцию foo засунуть в отдельную динамическую либу (aka .so) -- в этом случае у компилятора меньше возможностей суровой оптимизации.
Впрочем, наверняка и статическую либу можно (.a) попробовать.
-
Проверил с выносом foo в динамическую либу (g++) - все так же.
-
Не оО, а o0.
8)
так вот в чем фишка... "истина" нуле, а я то думал.. впрочем воистину С++ язык для не очень простых людей, я бы сказал совсем мне простых... это надо же так
х..ню напишешь, запустишь, просишь эмулировать... а потом пару дней думаешь... а что бы это значило.
-
Не оО, а o0.
8)
так вот в чем фишка... "истина" нуле, а я то думал.. впрочем воистину С++ язык для не очень простых людей, я бы сказал совсем мне простых... это надо же так
х..ню напишешь, запустишь, просишь эмулировать... а потом пару дней думаешь... а что бы это значило.
Да нет, с тем что там просил эмулировать благополучно разобрались же - ссылка не эквивалентна указателю и через указатель не эмулируется в общем случае.
Осталось разобраться, в одном конкретном случае, эквивалентна ли ссылка на константу самой константе. Хотя, если подумать... Ссылка это алиас, алиас на константу остается той же самой константой. В чем вопрос, спрашивается? :-)
-
В чем вопрос, спрашивается? :-)
На фига козе боян? - кажется так.. :D
-
const O o1 = foo();
nt i;
cin >> i;
1 раз создаётся объект, затем ожидание ввода, затем он удаляется. GCC не генерирует лишнее копирование.
const O &o1 = foo();
nt i;
cin >> i;
Результат идентичен. Что и требовалось доказать.
В чистом примере от Алексея деструктор вызывался из-за того, что элементы в локальных блоках помещены.
P.S. Отключил оптимизацию, но результат не изменился.
-
const O o1 = foo();
nt i;
cin >> i;
1 раз создаётся объект, затем ожидание ввода, затем он удаляется. GCC не генерирует лишнее копирование.
const O &o1 = foo();
nt i;
cin >> i;
Результат идентичен. Что и требовалось доказать.
В чистом примере от Алексея деструктор вызывался из-за того, что элементы в локальных блоках помещены.
P.S. Отключил оптимизацию, но результат не изменился.
Эмм... Ты всего лишь показал то, о чем я говорил - в том примере действительно идентичны поведения были и требовалось поставить такой эксперимент, который выявил бы различия. В блоки я переменные засунул для того, чтобы сгруппировать конструктор-деструктор для каждой из переменной (и там тоже было симметрично). То есть чтобы было не: O(), O(), ~O(), ~O(), а O(),~O(),O(),~O()
-
Итак, отличия таки нашлись.
Ну, во-первых:
если у нас foo() внезапно начнет возвращать не переменную типа O, а таки O& (решили соптимизировать - возвращаем некую долгоживующую переменную, нет смысла создавать локальную копию её), то все внезапно поменяется:
#include <iostream>
using namespace std;
struct O {
int foo;
O(int i) : foo(i) {cout << "O(" << i <<")\n";}
O(const O& r) {foo=r.foo; cout << "Copy constructor\n";}
~O(){cout << "~O(" << foo << ")\n";}
O& operator=(const O& r) {cout << "=\n"; return *this;}
};
O o(0);
O& foo() {
return o;
}
int main() {
cout << "=== begin ===\n";
{const O& o0 = foo();}
cout << "--- mmMmm ---\n";
{const O o1 = foo();}
cout << "=== end ===\n";
return 0;
}
Программа напишет теперь такое:
O(0)
=== begin ===
--- mmMmm ---
Copy constructor
~O(0)
=== end ===
~O(0)
То есть в случае const O o1 = foo(); будет создавать копию переменной (вызывая для этого копирующий конструктор), а const O& = foo(); как было оптимальным так и останется. Никто ничего не заметит.
http://liveworkspace.org/code/1vbybT$1
Это первое семантическое отличие. А вот второе:
struct Foo
{
Foo(Foo &&)=delete;
Foo(int,int) {}
};
Foo create() { return {12,42}; }
int main()
{
const Foo &t1 = create(); // OK
const Foo t2 = create(); // error
}
Во втором случае (t2) требуется от типа Foo наличие move semantic (семантики перемещения). http://liveworkspace.org/code/3ylY4h$21
Все это отрылось по результатам обсуждения на rsdn: http://rsdn.ru/forum/cpp/5104989.flat.1
-
Итак, отличия таки нашлись.
Ну, во-первых:
если у нас foo() внезапно начнет возвращать не переменную типа O, а таки O& (решили соптимизировать - возвращаем некую долгоживующую переменную, нет смысла создавать локальную копию её), то все внезапно поменяется:
:D ГЫ... ЭТОТ вариант как раз и не интересовал меня...( поскольку он имеет смысл) в отличие от фигни предложенной вами в начале...
-
Итак, отличия таки нашлись.
Ну, во-первых:
если у нас foo() внезапно начнет возвращать не переменную типа O, а таки O& (решили соптимизировать - возвращаем некую долгоживующую переменную, нет смысла создавать локальную копию её), то все внезапно поменяется:
:D ГЫ... ЭТОТ вариант как раз и не интересовал меня...( поскольку он имеет смысл) в отличие от фигни предложенной вами в начале...
Какой именно?
-
Итак, отличия таки нашлись.
Ну, во-первых:
если у нас foo() внезапно начнет возвращать не переменную типа O, а таки O& (решили соптимизировать - возвращаем некую долгоживующую переменную, нет смысла создавать локальную копию её), то все внезапно поменяется:
:D ГЫ... ЭТОТ вариант как раз и не интересовал меня...( поскольку он имеет смысл) в отличие от фигни предложенной вами в начале...
Какой именно?
Самый первый - обычное ЗНАЧЕНИЕ возвращаемое функцией (содержащееся в локальной переменной ).
-
(решили соптимизировать - возвращаем некую долгоживующую переменную, нет смысла создавать локальную копию её), то все внезапно поменяется:
Вопрос - в каком месте прописана заведомая "оптимальность" выбора?
-
(решили соптимизировать - возвращаем некую долгоживующую переменную, нет смысла создавать локальную копию её), то все внезапно поменяется:
Вопрос - в каком месте прописана заведомая "оптимальность" выбора?
Оптимальность определяется в контексте конкретного кода, задачи, ресурсов и ТЗ.
-
:D ГЫ... ЭТОТ вариант как раз и не интересовал меня...( поскольку он имеет смысл) в отличие от фигни предложенной вами в начале...
Какой именно?
Самый первый - обычное ЗНАЧЕНИЕ возвращаемое функцией (содержащееся в локальной переменной ).
Это была всего лишь иллюстрация того, что семантика ссылки в С++ отличается от семантики любых указателей. То есть ссылка в С++ не является лишь синтаксическим сахаром над указателем.
Далее возникла задача (точнее мне просто стало интересно) показать отличие const Foo foo от const Foo& foo не в качестве аргументов функций. Что вполне удалось.
-
Оптимальность определяется в контексте конкретного кода, задачи, ресурсов и ТЗ.
ммм... не то.... ре формулирую вопрос... с чего где прописана ОБЯЗАННОСТЬ компилятора оптимизировать этот момент?
-
Оптимальность определяется в контексте конкретного кода, задачи, ресурсов и ТЗ.
ммм... не то.... ре формулирую вопрос... с чего где прописана ОБЯЗАННОСТЬ компилятора оптимизировать этот момент?
Дык, это не оптимизация. Тут имеем тупо алиас на алиас, алиасы, вообще говоря, не должны создавать новых переменных/объектов (в том числе не должны вызывать какие-либо конструкторы). Ну вот оно и не создает.
В случае const Foo bar; имеем не алиас а самостоятельную константу, то есть этот самый bar не должен измениться когда изменится оригинальное значение. Константность у ссылки и у переменной имеет разный смысл.
-
Оптимальность определяется в контексте конкретного кода, задачи, ресурсов и ТЗ.
ммм... не то.... ре формулирую вопрос... с чего где прописана ОБЯЗАННОСТЬ компилятора оптимизировать этот момент?
Дык, это не оптимизация. Тут имеем тупо алиас на алиас, алиасы, вообще говоря, не должны создавать новых переменных/объектов (в том числе не должны вызывать какие-либо конструкторы). Ну вот оно и не создает.
В случае const Foo bar; имеем не алиас а самостоятельную константу, то есть этот самый bar не должен измениться когда изменится оригинальное значение. Константность у ссылки и у переменной имеет разный смысл.
1. этот вопрос относился к приведенному в КОНЦЕ варианту фунюкции Foo (возвращающей ссылку на сторонний обьект) -где и нашлось различие.. где гарантия что оно будет наблюдаться во ВСЕХ реализациях компилятора С++
2. Хорошо , а как вы интерпретируете const int & i=45; ?
-
Дык, это не оптимизация. Тут имеем тупо алиас на алиас, алиасы, вообще говоря, не должны создавать новых переменных/объектов (в том числе не должны вызывать какие-либо конструкторы). Ну вот оно и не создает.
В случае const Foo bar; имеем не алиас а самостоятельную константу, то есть этот самый bar не должен измениться когда изменится оригинальное значение. Константность у ссылки и у переменной имеет разный смысл.
1. этот вопрос относился к приведенному в КОНЦЕ варианту фунюкции Foo (возвращающей ссылку на сторонний обьект) -где и нашлось различие.. где гарантия что оно будет наблюдаться во ВСЕХ реализациях компилятора С++
Я ровно на это и отвечал. Создание алиаса (ссылки) обязано НЕ ПРИВОДИТЬ к созданию нового объекта и соответственно вызову конструкторов. Иначе это будет не алиас, а самостоятельный объект.
2. Хорошо , а как вы интерпретируете const int & i=45; ?
Как алиас на число 45. Как именно это будет выглядеть в машкоде - дело десятое. Важна семантика.
-
Дык, это не оптимизация. Тут имеем тупо алиас на алиас, алиасы, вообще говоря, не должны создавать новых переменных/объектов (в том числе не должны вызывать какие-либо конструкторы). Ну вот оно и не создает.
В случае const Foo bar; имеем не алиас а самостоятельную константу, то есть этот самый bar не должен измениться когда изменится оригинальное значение. Константность у ссылки и у переменной имеет разный смысл.
1. этот вопрос относился к приведенному в КОНЦЕ варианту фунюкции Foo (возвращающей ссылку на сторонний обьект) -где и нашлось различие.. где гарантия что оно будет наблюдаться во ВСЕХ реализациях компилятора С++
Я ровно на это и отвечал. Создание алиаса (ссылки) обязано НЕ ПРИВОДИТЬ к созданию нового объекта и соответственно вызову конструкторов. Иначе это будет не алиас, а самостоятельный объект.
2. Хорошо , а как вы интерпретируете const int & i=45; ?
Как алиас на число 45. Как именно это будет выглядеть в машкоде - дело десятое. Важна семантика.
1. ХОРОШО - тогда вопрос в чем отличие этого варианта от банальной O &o (без модификатора const )
2. а как вы интерпретируете const int i=45; ?
-
Как алиас на число 45. Как именно это будет выглядеть в машкоде - дело десятое. Важна семантика.
1. ХОРОШО - тогда вопрос в чем отличие этого варианта от банальной O &o (без модификатора const )
O& o -- это алиас разрешающий модификацию, то есть он может быть только на не константу.
2. а как вы интерпретируете const int i=45; ?
Как создание константы i равной 45.
-
Да, нюанс, вот так можно написать:
volatile const int i = 45;
А вот так нельзя:
volatile const int& i = 45;
Ошибка компиляции.
Что в общем то логично - ссылка не создает новую переменную или объект, это тупо алиас. А вот определение константы создает новый объект/переменную.
-
А вот так нельзя:
volatile const int& i = 45;
Ошибка компиляции.
Что в общем то логично - ссылка не создает новую переменную или объект, это тупо алиас. А вот определение константы создает новый объект/переменную.
надо же, а у меня компилирует без проблем и предупреждений ;)
-
А вот так нельзя:
volatile const int& i = 45;
Ошибка компиляции.
Что в общем то логично - ссылка не создает новую переменную или объект, это тупо алиас. А вот определение константы создает новый объект/переменную.
надо же, а у меня компилирует без проблем и предупреждений ;)
У тебя компилятор явно не реализует стандарт С++. Такими компиляторами славится мелкософт, соответственно предполагаю, что собираешь ты студией (и скорее всего у тебя там не отключены нестандартные расширизмы мелкософтовые и изменения в языке).
Собственно разные вменяемые компиляторы можешь попробовать тут: http://liveworkspace.org/code/31Hll5$2
Ошибка такая:
Compilation finished with errors:
source.cpp:2:25: error: volatile lvalue reference to type 'const volatile int' cannot bind to a temporary of type 'int'
volatile const int& i = 45;
^ ~~
1 error generated.
-
У тебя компилятор явно не реализует стандарт С++. Такими компиляторами славится мелкософт, соответственно предполагаю, что собираешь ты студией (и скорее всего у тебя там не отключены нестандартные расширизмы мелкософтовые и изменения в языке).
Да , точно.
-
Итак, отличия таки нашлись.
Ну, во-первых:
если у нас foo() внезапно начнет возвращать не переменную типа O, а таки O& (решили соптимизировать - возвращаем некую долгоживующую переменную, нет смысла создавать локальную копию её), то все внезапно поменяется:
Разумеется. Присваивание указателя переменной указательного типа не вызывает лишних копирований.
Это первое семантическое отличие. А вот второе:
Небольшая магия, скрывающая отсутствие необходимости вызывать конструктор переноса, поскольку функции create передаётся адрес памяти, куда и будет помещён объект. Ровно такую же магию можно разрешить и указателям. Если в левой части присваивания указатель,а в правой — значение, то указателю будет присвоен адрес на стеке. Собственно, именно так скрыто и идёт работа со ссылками.
Я подытожу. Здесь и далее синоним — дополнительное имя объекта.
1) Ссылки — это автоматически разыменовываемые указатели. В ряде случаев их использование проще соптимизировать компилятору.
2) Ссылки не обязательно инициализируются один раз. Прыжок назад позволит повторить присваивание.
3) Ссылки не являются физическим синонимом объекта (alias), поскольку их срок жизни может быть больше срока жизни объекта, то есть ссылка может хранить адрес удалённого объекта или объекта на стеке. Ссылки не являются логическим синонимом объекта, так как их можно переустановить.
4) Ссылки могут содержать любые адреса, включая null. Абсолютно легальный код без предупреждений:
Struct* p = NULL;
Struct& ref = *p;
5) Физическая привязка к объекту невозможна без хранения информации о нём. Практически всегда (за вычетом оптимизаций) это адрес объекта. Следовательно, во-первых, ссылка занимает дополнительную память (синоним, как утверждают, памяти дополнительной не забирает), во-вторых, оперирует исключительно адресом, в независимости от его верности: в примере выше разыменование не вызывает исключения, а &ref возвращает 0. В-третьих, поскольку ссылка хранит указатель, то возникают накладные расходы на разыменование, чего не должно быть у синонима.
6) К ссылкам привязан ряд встроенных оптимизаций и сахара. Точно такие же оптимизации можно разрешить для указателей, поскольку на физическом уровне ничего не меняется.
7) Страуструп запретил переустанавливать ссылки по субъективной личной причине неудачного опыта с Algol68:
The reason that C++ does not allow you to rebind references is given in Stroustrup's "Design and Evolution of C++" :
It is not possible to change what a reference refers to after initialization. That is, once a C++ reference is initialized it cannot be made to refer to a different object later; it cannot be re-bound. I had in the past been bitten by Algol68 references where r1=r2 can either assign through r1 to the object referred to or assign a new reference value to r1 (re-binding r1) depending on the type of r2. I wanted to avoid such problems in C++.
Странное решение вместо универсального синтаксиса для установка адреса, который будет содержать ссылка. Повторяю, это не только оправдано и логично, но и уже было в алголе.
8) Указатель и ссылкы взаимоконвертируемы:
The C++ standard specifies that a cast reinterpret_cast<U&>(t) is equivalent to *reinterpret_cast<U*>(&t)
9) Личное субъективное наблюдение. С++ FAQ и адепты языка зачастую повторяют заученные мантры и в этом плане напоминают культистов. Очень странно, учитывая, что большинство из них практикующие профессионалы, понимающие, что стоит за высокопарными фразами, не имеющими к практике никакого отношения.
10) Ничего не имею против автоматически разыменовываемых указателей. Однако возводимая на пустом месте сложность и высокопарность лишь запутывает людей при изучении языка. Все магические свойства ссылок вытекают из того факта, что (*p) не может быть nullptr без вылетов при доступе к объекту. Однако все недостатки и свойства указателей сохраняются: удалённые объекты, нулевые указатели и адреса на стеке могут быть значениями ссылок.
-
1) Ссылки — это автоматически разыменовываемые указатели. В ряде случаев их использование проще соптимизировать компилятору.
Тут valexey уже кучу доводов привел почему ссылки - не указатели. Все мало? :)
2) Ссылки не обязательно инициализируются один раз. Прыжок назад позволит повторить присваивание.
Прыжок назад - это новая ссылка. Отвыкаем думать в терминах VAR секции на всю процедуру ;) Семантически переменная "создается" в месте объявления и разрушается в конце блока. Что там происходит в случае goto - даже думать не хочу (неинтересно). Вот в таком коде:
for(int i = 0; i < 10; i++)
{
int var = 1;
int const& ref = i;
}
переменная var и ссылка ref будет создана и разрушена 10 раз. Никаких "повторных присваиваний" тут нет.
4) Ссылки могут содержать любые адреса, включая null. Абсолютно легальный код без предупреждений:
Struct* p = NULL;
Struct& ref = *p;
Ссылка не может содержать NULL и приведенный код невалидный (UB в момент "*p"). Да, компиляция без предупреждений - частный случай UB.
7) Страуструп запретил переустанавливать ссылки по субъективной личной причине неудачного опыта с Algol68:
Вполне валидная причина, нет?
Странное решение вместо универсального синтаксиса для установка адреса, который будет содержать ссылка. Повторяю, это не только оправдано и логично, но и уже было в алголе.
Приведи хоть один пример где это нужно и который не может быть переписан без потери наглядности.
10) Ничего не имею против автоматически разыменовываемых указателей. Однако возводимая на пустом месте сложность и высокопарность лишь запутывает людей при изучении языка.
Так что делать с перегружаемыми операторами в отсутствии ссылок и в присутствии адресной арифметики? Например в шарпе (при том что там нет адресной арифметики) операторы перегружается довольно странно (с обязательным паттерном проверки на нулевость, хе-хе).
-
Ну и еще одно отличие ссылки от указателе - указатель это first-class citizen в С++. С ним можно делать ровно то же самое, что и с переменной любого другого типа. Ну, кроме всего прочего, у него например можно взять адрес.
А вот ссылка - нет. Это не first-class citizen. У ссылки нет собственного адреса, ссылку нельзя копировать. Не бывает указателя на ссылку (а вот указатель на указатель бывает!) и не бывает ссылки на ссылку.
Да, и в рассуждениях выше, у меня сложилось впечатление, что Berserker регулярно путает понятие адреса и указатея. Это две принципиально разные сущности :-D
В некоторых случаях реализация ссылки действительно делается через использование адресов, но ссылки никогда не используют указатели. Впрочем, это все уже нюансы какой-то конкретной реализации под какую-то конкретную платформу.
-
Тут valexey уже кучу доводов привел почему ссылки - не указатели. Все мало?
Не вижу ни одного примера, который бы противоречил моим словам.
Прыжок назад - это новая ссылка. Отвыкаем думать в терминах VAR секции на всю процедуру
Вообще-то я думаю в терминах реального железа, ассемблера и конструкций С++.
Что там происходит в случае goto - даже думать не хочу (неинтересно).
Там осуществляется переход назад командой jmp/jxx, так же, как и в случае цикла. После чего снова выполняется выражение и конкретная ячейка памяти, которую вы зовёте ссылкой, получает конкретное новое значение. Ну никакой магии и всё абсолютно в терминах и по правилам языка.
переменная var и ссылка ref будет создана и разрушена 10 раз. Никаких "повторных присваиваний" тут нет.
Про блоки мне уже пояснили.
Ссылка не может содержать NULL и приведенный код невалидный (UB в момент "*p"). Да, компиляция без предупреждений - частный случай UB.
Не может в каком мире? В каких компиляторах не может? Все проверки включены, ни одна низкоуровневая возможность не использована. Код абсолютно корректен с точки зрения языка. Не Вы ли писали, что ссылка гарантирована не NULL, не мёртвая и не висячая? Какой смысл писать то, что не соответствует действительности? А теперь попробуйте сделать то же самое в Обероне. Чтобы VAR-параметр вылетал при обращении к нему или содержал мусор. Вот Оберон может компилировать под разные целевые платформы с разными возможностями организации динамической памяти. А С++ ближе к железу всё-таки.
Вполне валидная причина, нет?
Нет.
1) Страуструп всё-таки оставил один и тот же синтаксис и для присваивания значения переменной и для инициализации ссылки.
2) И первый пункт разрулить и убрать неоднозначность можно было бы при помощи введения ключевого слова или нового оператора (например, ===).
Приведи хоть один пример где это нужно и который не может быть переписан без потери наглядности.
Элементарно. Мне нужно обменять адреса двух ссылок. То есть swap со скоростью указателей. В парсере PrevLexem и ThisLexem постоянно обмениваются. Негоже мне вызывать тормознутый std::swap с реальным копированием. Вот и приходится возвращаться к указателям. И да, лексемы нужно уметь устанавливать (на самом деле устанавливать приходится много полей, которые можно было бы сделать ссылками). При этом синтаксис указателей мне абсолютно не нужен. Он неудобен и излишен (тоже один из вопросов критики С++: два оператора для доступа к элементам структур).
Так что делать с перегружаемыми операторами в отсутствии ссылок
А зачем убирать ссылки? Достаточно дать возможность их менять, обменивать и т.д. Хотя скажу честно. Если бы доступ к полям структур был единым через "." и для переменных-указателей и для обычных, то ссылки стали бы менее нужны. Фактически, у них осталось бы только инициализация при объявлении возможно(!) корректным значением.
В некоторых случаях реализация ссылки действительно делается через использование адресов, но ссылки никогда не используют указатели. Впрочем, это все уже нюансы какой-то конкретной реализации под какую-то конкретную платформу.
Какие компиляторы реализуют ссылки не как указатели и пользовались ли Вы такими хоть раз в жизни? Представляете ли Вы себе реализацию компилятора по стандарту С++, где ссылки на физическом уровне не являются указателями? Указатель — это ячейка памяти (2, 4, 8 байтов), хранящая адрес блока памяти.
Ну и еще одно отличие ссылки от указателе - указатель это first-class citizen в С++. С ним можно делать ровно то же самое, что и с переменной любого другого типа. Ну, кроме всего прочего, у него например можно взять адрес.
Товарищи, смотрите:
1) В язык добавляется ссылка как автоматически разыменовываемый указатель. При присваивании адреса (а за именем переменной стоит её адрес, за возвращаемой из функции ссылкой — адрес и т.д) он ведёт себя как указатель. Во во всех остальных случаях как (*p).
2) Добавляется синтаксический сахар с целью оптимизаций. Такой сахар можно добавить и указателям. Он имеет под собой исключительно физическую подоплёку.
3) Накладывается ряд ограничений на использование ссылок в языке. Эти ограничения облегчают оптимизации, да, но не меняют физического/логического смысла ссылок и в свою очередь сужают возможности для их применения.
-
В некоторых случаях реализация ссылки действительно делается через использование адресов, но ссылки никогда не используют указатели. Впрочем, это все уже нюансы какой-то конкретной реализации под какую-то конкретную платформу.
Какие компиляторы реализуют ссылки не как указатели и пользовались ли Вы такими хоть раз в жизни? Представляете ли Вы себе реализацию компилятора по стандарту С++, где ссылки на физическом уровне не являются указателями? Указатель — это ячейка памяти (2, 4, 8 байтов), хранящая адрес блока памяти.
Да, конечно пользовался и пользуюсь. Да, все ими пользуются. :-)
Простой пример:
int main() {
volatile int i;
volatile int& rep = i;
volatile int* volatile ptr = &i;
i = 1;
rep = 2;
*ptr = 3;
return 0;
}
Берем clang и генерим код. Смотрим в сгенереный код:
define i32 @main() nounwind {
%i = alloca i32, align 4 ; <i32*> [#uses=3]
%ptr = alloca i32*, align 4 ; <i32**> [#uses=2]
volatile store i32* %i, i32** %ptr
volatile store i32 1, i32* %i
volatile store i32 2, i32* %i
%1 = volatile load i32** %ptr ; <i32*> [#uses=1]
volatile store i32 3, i32* %1
ret i32 0
}
Найди мне тут пожалуйста "указатель" через который реализовалась ссылка ref. Собственно под ref вообще ничего не выделяется ни на стеке, ни где-то там еще. Её в сгенереном коде нет вовсе.
-
Потому что это один из банальных моментов, который оптимизируется на ура.
Ведь локальная i - это что-то из серии EBP-4. То есть адреса, которые известны на этапе компиляции, разумеется не используются. А вот если у вас будет возврат ссылки из функции, то гляньте код. Там будет адрес.
-
Потому что это один из банальных моментов, который оптимизируется на ура.
Ведь локальная i - это что-то из серии EBP-4. То есть адреса, которые известны на этапе компиляции, разумеется не используются. А вот если у вас будет возврат ссылки из функции, то гляньте код. Там будет адрес.
Ты недооцениваешь современные компиляторы и C++ :-)
int some = 13;
int& two() {return some;}
int main() {
int& ref = two();
ref = 42;
return 0;
}
Сгенерированный код:
@some = global i32 13, align 4 ; <i32*> [#uses=2]
define i32* @_Z3twov() nounwind readnone {
ret i32* @some
}
define i32 @main() nounwind {
store i32 42, i32* @some
ret i32 0
}
Покажи мне тут, пожалуйста, где у нас ссылка в сгенерированном коде :-)
Вообще, это очень распространенная ошибка - пытаться разобраться в семантике языка пероецируя все в байтики и инструкции процессора (причем в сверически-наивной реализации под какую-то одну конкретную архитектуру). Эдак можно додуматься до того, что в обероне локальные переменные все на стеке всегда живут :-)
-
Потому что это один из банальных моментов, который оптимизируется на ура.
Ведь локальная i - это что-то из серии EBP-4. То есть адреса, которые известны на этапе компиляции, разумеется не используются. А вот если у вас будет возврат ссылки из функции, то гляньте код. Там будет адрес.
Да, и я не знаю что такое EBP-4. Скажем в процессорах с которыми я имею дело, такой штуки в принципе нет. :-)
-
Адрес локальной переменной для текущей процедуры. Не суть. Была бы глобальная переменная по адресу 0x400000, для ссылки точно так же ничего бы не генерировалось, так как вместо (*p) имеем (*((T*) 0x400000)).
Покажи мне тут, пожалуйста, где у нас ссылка в сгенерированном коде :-)
Эм, процедура встроилась (inlined), затем то, что указано выше. Ровно так же можно и с указателем оптимизировать, если агрессивно.
Алексей, Вы как будто не поняли моих слов. Ссылки при известном строгом адресе оптимизируются. Но это простые случаи. Когда же имеем код общего вида, где ссылка устанавливается в конструкторе или функция возвращает созданный объект, то оптимизации уже невозможны и адрес приходится хранить. А по поводу оптимизаций, так даже неиспользуемые переменные можно выкидывать, функции встраивать, циклы раскручивать. Только о чём это говорит кроме факта оптимизации?
-
Вообще-то я думаю в терминах реального железа, ассемблера и конструкций С++.
В терминах реального железа и ассемблера ссылка в большинстве случае будет выглядеть как ячейка с адресом. И этим идентична указателю. С этим никто не спорит. Но как конструкция С++ она довольно сильно отличается от указателя. Этими отличиями можно пользоваться во благо (я там выше писал, почему ссылки полезны). В том числе и тем, что ссылка инициализируется один раз.
Ссылка не может содержать NULL и приведенный код невалидный (UB в момент "*p"). Да, компиляция без предупреждений - частный случай UB.
Не может в каком мире?
В мире корректных C++ программ. Код разыменования нулевого указателя не корректен с точки зрения языка. Потому что это явно прописанный в стандарте UB. Т.е., ты теоретически можешь найти компилятор, который будет проверять в рантайме разыменование нулевого указателя и форматировать жесткий диск в этом случае. И такой компилятор при этом может соответсвовать стандарту С++, а твоя программа с таким разыменованием - точно нет.
Почему такая проверка не делается в существующих популярных реализация тоже понятно - эффективность с одной стороны и куча говнокода с другой.
Не Вы ли писали, что ссылка гарантирована не NULL, не мёртвая и не висячая? Какой смысл писать то, что не соответствует действительности?
Она гарантировано не NULL если в программе нет разыменования нулевых указателей. Т.е., в корректной С++ программе. Про мертвые/висячие я вообще ничего не говорил.
А теперь попробуйте сделать то же самое в Обероне. Чтобы VAR-параметр вылетал при обращении к нему или содержал мусор.
В обероне сематника VAR параметра отличается от семантики ссылки в С++. В частности в обероне ты не может вернуть VAR или положить его как поле структуры.
1) Страуструп всё-таки оставил один и тот же синтаксис и для присваивания значения переменной и для инициализации ссылки.
2) И первый пункт разрулить и убрать неоднозначность можно было бы при помощи введения ключевого слова или нового оператора (например, ===).
Мне кажется, что оно просто того не стоит. Обратимся к примеру.
Элементарно. Мне нужно обменять адреса двух ссылок. То есть swap со скоростью указателей.
Зачем? Я не издеваюсь. Просто нет такой задачи самой по себе.
В парсере PrevLexem и ThisLexem постоянно обмениваются.
Ну так меняй указатели, в чем проблема-то? Указатели для того и нужны, чтоб указывать на разные штуки во времени. Приведи, плз, более развернеутый пример, пока не очень догоняю.
-
Адрес локальной переменной для текущей процедуры. Не суть. Была бы глобальная переменная по адресу 0x400000, для ссылки точно так же ничего бы не генерировалось, так как вместо (*p) имеем (*((T*) 0x400000)).
У меня там как раз глобальная переменная ссылка на которую возвращается через функцию которая вызывается из другой функции и "присваивается" локальной ссылке.
Еще раз - ссылка это алиас. Это не first-class citizen. Поэтому ссылка штука во-первых более безопасная чем указатель, во-вторых более эффективная и местами более фичастая (ибо позволяет например дать имя результату какого-либо выражения не заводя новую переменную).
Иногда ссылки да, реализуются через адреса. Но реже чем указатели. Семантика ссылок и указателей РАЗНАЯ и ссылки не являются простым синтаксическим сахаром над семантикой указателей.
Если нужна семантика указателей, и ссылка не годится, то следует (в подавляющем большенстве случаев)... не пользоваться указателями, а воспользоваться умными указателями. Голые указатели - они только для крайних случаев (например для реализации умных указателей, для реализации своих менеджеров памяти и так далее).
-
Еще к вопросу об UB, говнокоде и отсутвии нормальной диагностики разыменования нулевых указателей. Вот метод весьма популярного класса весьма популярной библиотеки весьма промышленной конторы M$:
HWND CWnd::GetSafeHwnd() const { return this == NULL ? NULL : m_hWnd; }
Существование этого кода говорит лишь о том, что код гавно, а автор мудак. А не о том, что this в С++ может быть нулем.
-
Еще к вопросу об UB, говнокоде и отсутвии нормальной диагностики разыменования нулевых указателей. Вот метод весьма популярного класса весьма популярной библиотеки весьма промышленной конторы M$:
HWND CWnd::GetSafeHwnd() const { return this == NULL ? NULL : m_hWnd; }
Существование этого кода говорит лишь о том, что код гавно, а автор мудак. А не о том, что this в С++ может быть нулем.
Пахнуло MFC :-)
-
Ну вот еще пример.
#include <stdlib.h>
#include <stdio.h>
int two() {return random()%42;}
int main() {
const int& ref = two();
printf("%d\n", ref);
return 0;
}
Сгенерированный код:
define i32 @_Z3twov() {
%1 = tail call i32 @random() ; <i32> [#uses=1]
%2 = srem i32 %1, 42 ; <i32> [#uses=1]
ret i32 %2
}
declare i32 @random()
define i32 @main() {
%1 = tail call i32 @random() ; <i32> [#uses=1]
%2 = srem i32 %1, 42 ; <i32> [#uses=1]
%3 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %2) ; <i32> [#uses=0]
ret i32 0
}
Задача - найти адрес которым пользуется ref, ну и саму ref заодно :-)
-
Ну так меняй указатели, в чем проблема-то? Указатели для того и нужны, чтоб указывать на разные штуки во времени. Приведи, плз, более развернеутый пример, пока не очень догоняю.
class Parser
{
protected:
...
Lexem this_lexem_data;
Lexem prev_lexem_data;
Lexem *this_lexem;
Lexem *prev_lexem;
...
enum kErrorLocation
{
kThisLexem,
kPrevLexem
};
public:
Parser ():
this_lexem(&this_lexem_data),
prev_lexem(&prev_lexem_data)
{}
void MarkError (const string &error, kErrorLocation error_location)
{
const Lexem &lexem = (error_location == kThisLexem) ? *this_lexem : *prev_lexem;
log
(
error + ". Error in file \"" + config_file_name + "\" on line "
+ ToStr(lexem.line) + " at position " + ToStr(lexem.line_pos),
"ParseModConfig",
kMsgError
);
}
bool ReadLexem ()
{
swap(this_lexem, prev_lexem);
bool result = vfs::ReadLexem(config, *this_lexem);
if (this_lexem->type == kLexError)
{
MarkError(this_lexem->error, kThisLexem);
}
return result;
}
...
Можно было бы использовать ссылки. А так приходится довольствоваться синтаксисом "->", "*Lexem" и т.д.
Поэтому ссылка штука во-первых более безопасная чем указатель,
Да нет. В дубовом компиляторе в лоб всегда бы выделялся временный объект. От того, что мёртвый код вычищается, а маленькие функции и циклы и вовсе встраиваются, ничего не меняется. Более того, вы теряете понятие накладных расходов на хранение и разыменование, а в большинстве случаев ссылки всё же не указывают на соседние переменные в кадре стека или глобальные объекты, или указывают, но не так банально.
int some = 13;
int& two() {return some;}
int main() {
int& ref = two();
ref = 42;
return 0;
}
=>
int *ref = &some;
(*ref) = 42
=>
*(&some) = 42
=>
some = 42
Задача - найти адрес которым пользуется ref, ну и саму ref заодно :-)
Функция встроилась. Далее используется ref в качестве аргумента. Больше нигде ref не используется. Поэтому временный результат не сохраняется. А вы вот всё же вызовите вслед функцию из внешней среды, например WinAPI с этим самым ref. И он магическим образом обретёт себе хранилище.
Я знаком с вариантами оптимизаций со стороны компилятора (приходится и с обратным проектированием иметь дело), не вижу никакой магии.
Существование этого кода говорит лишь о том, что код гавно, а автор мудак. А не о том, что this в С++ может быть нулем.
Хотел ровно ту же фичу применить для JSon объектов. Вызов метода ведёт к проверке this на null (UB не будет, если класс не содержит виртуальных функций), если null — то это пустое значение, обрабатываемое отдельно. Почему же автор мудак?
-
При том, что никто не мешал (кроме ООП головного мозга) сделать свободную функцию (или static метод) с требуемой семнтикой и абсолютно корректную с точки зрения C++:
HWND GetSafeHwnd(CWnd* wnd) const { return wnd == NULL ? NULL : wnd->m_hWnd; }
P.S. Не зря Страуструп жалел, что не сделал this ссылкой, а не указателем. Глядишь такого гавна в MFC было бы меньше.
-
А проверка на &this == nullptr не спасла бы отца русской демократии в таком случае? :)
-
Можно было бы использовать ссылки. А так приходится довольствоваться синтаксисом "->", "*Lexem" и т.д.
Декомпозиция и инкапсуляция рулит:
class Lexems
{
public:
Lexems():
this_lexem(&this_lexem_data),
prev_lexem(&prev_lexem_data)
{}
Lexem& current(){return *this_lexem;}
Lexem& prev(){return *prev_lexem;}
void swap(){std::swap(this_lexem, prev_lexem);}
private:
Lexem this_lexem_data;
Lexem prev_lexem_data;
Lexem *this_lexem;
Lexem *prev_lexem;
};
Все указатели остались во внутренних потрохах Lexems, наружу торчат только ссылки и человеческие методы (возможно swap надо переименовать во что-то более осмысленное). Не? Или lexems.current()/lexems.prev() вызывают нарекания лишними скобочками? Тогда ничем помочь не могу :)
Существование этого кода говорит лишь о том, что код гавно, а автор мудак. А не о том, что this в С++ может быть нулем.
Хотел ровно ту же фичу применить для JSon объектов.
Обрати внимание на мое дополнение к тому посту (про свободную функцию) плз. Прежде чем плодить говнокод.
Вызов метода ведёт к проверке this на null (UB не будет, если класс не содержит виртуальных функций), если null — то это пустое значение, обрабатываемое отдельно. Почему же автор мудак?
Потому что this не может быть нулем. Вызов метода на нулевом this это ничто иное как все то же разыменование нулевого указателя (p->method()), что является оговоренным UB. Виртуальность здесь постольку поскольку (твоему компилятору все-таки придется слазить по нулевому указателю в таблицу виртуальных функций и обломаться).
-
Функция встроилась. Далее используется ref в качестве аргумента. Больше нигде ref не используется. Поэтому временный результат не сохраняется. А вы вот всё же вызовите вслед функцию из внешней среды, например WinAPI с этим самым ref. И он магическим образом обретёт себе хранилище.
Не понял, а для кого я printf вызывал? Это как раз и есть та самая фукнция из внешней среды. Могу еще раз вызвать - результат не изменится :-) C WinAPI (если бы он существовал на линуксе) было бы то же самое.
-
Обрати внимание на мое дополнение к тому посту (про свободную функцию) плз. Прежде чем плодить говнокод.
Так я и пришёл к выводу, что null в качестве реального объекта безопаснее. Тем не менее, для класса без наследников, родителей и виртуальных функций обращения к VMT нет, как и самой VMT.
Не понял, а для кого я printf вызывал? Это как раз и есть та самая фукнция из внешней среды. Могу еще раз вызвать - результат не изменится :-) C WinAPI (если бы он существовал на линуксе) было бы то же самое.
Так добавьте ещё один вызов того же printf, но уже с ref * 2. Второй вызов и последующее использование ref не дадут его соптимизировать.
-
А проверка на &this == nullptr не спасла бы отца русской демократии в таком случае? :)
Есть надежда, что писатель лишний раз бы задумался - как может получиться такой объект у которого адрес (&) нулевой. И на стал бы пистаь такую херню. Потому как в случае установки "this - это указатель" шансов задуматься чуть меньше (указатели естественно могут быть нулевыми). Хотя гарантий, конечно, никаких нет...
-
Все указатели остались во внутренних потрохах Lexems, наружу торчат только ссылки и человеческие методы (возможно swap надо переименовать во что-то более осмысленное). Не? Или lexems.current()/lexems.prev() вызывают нарекания лишними скобочками? Тогда ничем помочь не могу :)
Откровенно говоря, я вообще не вижу в данном случае тут делать prev/current тут, падон, членами. См. begin, end в современном stl'e например. Если прямо таки хочется инкапсуляции, то эти функции не члены могут быть френдами.
Тогда это все будет выглядеть как prev(lexems) и current(lexems), swap(lexems).
-
Не понял, а для кого я printf вызывал? Это как раз и есть та самая фукнция из внешней среды. Могу еще раз вызвать - результат не изменится :-) C WinAPI (если бы он существовал на линуксе) было бы то же самое.
Так добавьте ещё один вызов того же printf, но уже с ref * 2. Второй вызов и последующее использование ref не дадут его соптимизировать.
Эмм.. С чего бы?
define i32 @_Z3twov() {
%1 = tail call i32 @random() ; <i32> [#uses=1]
%2 = srem i32 %1, 42 ; <i32> [#uses=1]
ret i32 %2
}
declare i32 @random()
define i32 @main() {
%1 = tail call i32 @random() ; <i32> [#uses=1]
%2 = srem i32 %1, 42 ; <i32> [#uses=2]
%3 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %2) ; <i32> [#uses=0]
%4 = shl i32 %2, 1 ; <i32> [#uses=1]
%5 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %4) ; <i32> [#uses=0]
ret i32 0
}
Ровно то же самое. Никаких указателей.
-
Так я и пришёл к выводу, что null в качестве реального объекта безопаснее.
NULL не может быть объектом по определению. NULL - это нулевой указатель и ничего более. Такой указатель не может указывать на объект (явно огворено в стандарте). Даже в экзотических архитектурах, где объекты могут быть размещены по нулевому (0x0000 в терминах железки) адресу, указатель на такой объект (this) не будет равен NULL (потому что NULL будет представлен там каким-то другим значением).
-
Тогда это все будет выглядеть как prev(lexems) и current(lexems), swap(lexems).
Угу. Так тоже можно. Смысл все тот же - не вываливать потроха на пользователя класса и иметь полный контроль над этими потрохами.
-
Алексей, расходы на локальную память растут: %1-%4. В локальной памяти хранятся результаты вызовов функций, а указатели не нужны, потому что указатели и есть адреса ячеек %1-%4.
*(&%1) превращается просто в упоминание %1.
Кстати, а как дела с указателями?
int i;
int main ()
{
int* p = &i;
(*p) = 42;
printf("%d", *p);
return 0;
}
Хороший компилятор соптимизирует до printf("%d", i), что не повысит статус указателя до магического, а просто в очередной раз подтвердит высокий уровень оптимизации.
NULL не может быть объектом по определению. NULL - это нулевой указатель и ничего более. Такой указатель не может указывать на объект (явно огворено в стандарте). Даже в экзотических архитектурах, где объекты могут быть размещены по нулевому (0x0000 в терминах железки) адресу, указатель на такой объект (this) не будет равен NULL (потому что NULL будет представлен там каким-то другим значением).
Имеется в виду распространённая практика null с малой буквы, то есть реальный объект, играющий роль пустышки.
TObject null_obj;
TObject* null = &null_obj;
-
Я бы всё же поднял время редактирования сообщений до минут 5.
Хороший компилятор соптимизирует до printf("%d", i=42).
-
TObject null_obj;
TObject* null = &null_obj;
Такое решение возможно, но тут правильнее называть такой объект не "null", а "default". И если под практикой имеется ввиду инициализация всех указателей всегда таким умолчательным значением (вместо NULL) - то я против такой практики (похоже на попытку решения проблемы не самыми эффективными средствами).
-
null — это тип данных и одновременное значение в JavaScript и, в частности, JSON (http://json.org/) формате (собственно, Вы это и без меня знаете :) ). Поэтому имя default как-то не отвечает смыслу. В С++ ведь заняты только NULL и nullptr (макрос и стандартизированное значение).
Нет, поголовно указатели не инициализируются им.
-
Алексей, расходы на локальную память растут: %1-%4. В локальной памяти хранятся результаты вызовов функций, а указатели не нужны, потому что указатели и есть адреса ячеек %1-%4.
Не растут. В IL LLVM просто нет изменяемых "регистров". Каждое новое сложение/умножение/вычитание всегда порождает очередной %n.
-
Мда ,.. господа...вам не кажется что закладываться на гипотетическую (или конкретную) реализацию яп... трактуя особенности семантики .. все равно , что закладываться на UB (аргументация уровня ... а если бы у бабушки были яйца .., или мой дедушка делает так.., или - пожилые люди обычно делают так).. как -то несерьезно ..
-
Ну и FFFFFUUUUUUUUUU этот ваш С++ о_О
-
Ну и FFFFFUUUUUUUUUU этот ваш С++ о_О
В плане завязки на оптимизации хаскель еще хуже :-)
-
Мда ,.. господа...вам не кажется что закладываться на гипотетическую (или конкретную) реализацию яп... трактуя особенности семантики .. все равно , что закладываться на UB (аргументация уровня ... а если бы у бабушки были яйца .., или мой дедушка делает так.., или - пожилые люди обычно делают так).. как -то несерьезно ..
Дык я лично например не закладываюсь. Я вообще всеми силами противился опускания дискуссии до уровня битиков и байтиков конкретной реализации под конкретную платформу. Ибо в байтиках семантику языка не увидеть, максимум что будет видно - одна из проекций оной семантики на конкретную реализацию.
-
Ну и FFFFFUUUUUUUUUU этот ваш С++ о_О
:D Geniepro --- вы прям как животное (делаете то что нравится, работаете на том что нравится...)
-
Дык я лично например не закладываюсь. Я вообще всеми силами противился опускания дискуссии до уровня битиков и байтиков конкретной реализации под конкретную платформу. Ибо в байтиках семантику языка не увидеть, максимум что будет видно - одна из проекций оной семантики на конкретную реализацию.
Я заметил... что практически ЛЮБОЙ разговор на ЛЮБОМ форуме о С++ сводится к этому.. с чего бы это? :D
-
Потому что без более-менее точного представления о физической реализации невозможно писать хороший код с точки зрения эффективности. А в С++ в ряде мест используется магия слов и понятий, затуманивающих явления. Те же ссылки невозможно реализовать без указателей в общем случае. Более того, у них все свойства указателей де-факто сохраняются. При этом FAQ пишет такие вещи как «Reference IS Object», что просто улыбка с лица не сходит.
-
Потому что без более-менее точного представления о физической реализации невозможно писать хороший код с точки зрения эффективности. А в С++ в ряде мест используется магия слов и понятий, затуманивающих явления. Те же ссылки невозможно реализовать без указателей в общем случае. Более того, у них все свойства указателей де-факто сохраняются. При этом FAQ пишет такие вещи как «Reference IS Object», что просто улыбка с лица не сходит.
Если нужна эффективная реализация (с точки зрения максимальной отдачи от железа)- используйте ассемблер... меня раздражает другое какого хрена тыкают в реализацию даже в том случае , когда исходная задача не формулируется в низкоуровневых моделях.. лично я вижу одно обьяснение этому - тяжелое наследие прошлого...
-
Потому что без более-менее точного представления о физической реализации невозможно писать хороший код с точки зрения эффективности. А в С++ в ряде мест используется магия слов и понятий, затуманивающих явления. Те же ссылки невозможно реализовать без указателей в общем случае. Более того, у них все свойства указателей де-факто сохраняются. При этом FAQ пишет такие вещи как «Reference IS Object», что просто улыбка с лица не сходит.
Если нужна эффективная реализация (с точки зрения максимальной отдачи от железа)- используйте ассемблер... меня раздражает другое какого хрена тыкают в реализацию даже в том случае , когда исходная задача не формулируется в низкоуровневых моделях.. лично я вижу одно обьяснение этому - тяжелое наследие прошлого...
+1
PS. А ссылки нельзя в общем случае реализовать без использования "указателей", а точнее адресов, но верно и другое - семантику ссылок нельзя реализовать используя ТОЛЬКО указатели.
-
Если нужна эффективная реализация (с точки зрения максимальной отдачи от железа)- используйте ассемблер... меня раздражает другое какого хрена тыкают в реализацию даже в том случае , когда исходная задача не формулируется в низкоуровневых моделях.. лично я вижу одно обьяснение этому - тяжелое наследие прошлого...
Значит все игры и ресурсоёмкие приложения, включая браузеры, нужно писать на ассемблере? Вы попишите немного, поотлаживайте. Потом скажите, какой там коэффициент полезного действия и при чём тут ассемблер, если С++ включает в себя максимальное число возможностей, присутствующих исключительно с целью контроля эффективности.
-
Если нужна эффективная реализация (с точки зрения максимальной отдачи от железа)- используйте ассемблер... меня раздражает другое какого хрена тыкают в реализацию даже в том случае , когда исходная задача не формулируется в низкоуровневых моделях.. лично я вижу одно обьяснение этому - тяжелое наследие прошлого...
Значит все игры и ресурсоёмкие приложения, включая браузеры, нужно писать на ассемблере? Вы попишите немного, поотлаживайте. Потом скажите, какой там коэффициент полезного действия и при чём тут ассемблер, если С++ включает в себя максимальное число возможностей, присутствующих исключительно с целью контроля эффективности.
Дешевая патетика.. - не все "игры и приложения".. а критические по производительности их части. Не вижу большого смысла в этих "максимальных возможностях" - если описание САМОГО ЯП не гарантирует их эффективную реализацию.. так... гребанные танцульки вокруг идолища, да срач на форумах... как пример... та же xds... при гораздо меньших данных для оптимизации на высокоуровневых задачах , обеспечивающая одинаковую производительность с лучшими компиляторами си того времени...
-
В терминах реального железа и ассемблера ссылка в большинстве случае будет выглядеть как ячейка с адресом.
Даже не с адресом, а со словом данных. И этим идентична указателю. С этим никто не спорит. Но как конструкция С++ она довольно сильно отличается от указателя. Этими отличиями можно пользоваться во благо (я там выше писал, почему ссылки полезны). В том числе и тем, что ссылка инициализируется один раз.
Думаю, как конструкция она больше всего похожа на директивы. Это сущность периода компиляции, указание компилятору. Если бы я захотел сделать реализацию своего языка, то в нём бы это было именно так (но как в плюсах, естественно, не знаю).
Что же касается беспокойства относительно места хранения данных и способа доступа к ним через ссылку, то я бы объяснял это так. Если объект создаётся в куче, то для обращения к нему нужно знать его адрес в памяти. Хранением адреса занимается указатель. Поэтому, в данном случае без указателя не обойтись. Но плюсовый указатель сам по себе имеет родовые проблемы, своеобразное решение которых было найдено через сколько-то десятков лет после создания языка светлыми C++ головами. Этим решением оказалась эфемерная обёртка вокруг интерфейса доступа к данным. В частности, у указателя есть богатый интерфейс, через который можно обращаться к данным различными способами. А ссылка служит эфемерной оболчкой вокруг этого интерфейса, оставив только способы доступа, которые светлые головы посчитали уместными, и сокрыв остальные.
Таким образом, если функция из внешней библиотеки возвращает данные, сохранённые в динамической памяти, а здесь мы присваиваем результат функции ссылке, то во время компиляции будет предусмотрено выделение памяти на стеке для указателя. Однако, поскольку указатель не объявлен в тексте программы, то доступа к нему нет. Но зато была объявлена ссылка, и на этапе компиляции проверяются способы доступа к ссылке, и если они корректны, то доступ переадесуется безымянному указателю.
-
Ну и FFFFFUUUUUUUUUU этот ваш С++ о_О
:D Geniepro --- вы прям как животное (делаете то что нравится, работаете на том что нравится...)
Насчёт животных не скажу, но ставить правильные цели и добиваться их - признак счастливого человека.
-
Думаю, как конструкция она больше всего похожа на директивы. Это сущность периода компиляции, указание компилятору. Если бы я захотел сделать реализацию своего языка, то в нём бы это было именно так (но как в плюсах, естественно, не знаю).
Собственно любой ЯВУ отличает именно то, что в нем есть то, что затем не отображается в машкод. Ну, например типы (их в машкоде просто нет) и все что с ними связано (например проверки валидности приведения типов на этапе компиляции).
-
Ну и FFFFFUUUUUUUUUU этот ваш С++ о_О
:D Geniepro --- вы прям как животное (делаете то что нравится, работаете на том что нравится...)
Насчёт животных не скажу, но ставить правильные цели и добиваться их - признак счастливого человека.
С этой фразой согласен... вообще.. не очень понимаю только как она следует из оригинальной фразы (Geniepro ), в особенности торкает слово "правильные" :D ;)
-
С этой фразой согласен... вообще.. не очень понимаю только как она следует из оригинальной фразы (Geniepro ), в особенности торкает слово "правильные" :D ;)
— Почти все люди говорят себе, что поступают правильно ... . Но это не поднимает их над заурядностью.
http://www.fanfics.ru/read.php?id=40982&chapter=69
-
С этой фразой согласен... вообще.. не очень понимаю только как она следует из оригинальной фразы (Geniepro ), в особенности торкает слово "правильные" :D ;)
— Почти все люди говорят себе, что поступают правильно ... . Но это не поднимает их над заурядностью.
http://www.fanfics.ru/read.php?id=40982&chapter=69
ерунда... цель оценок этого уровня.. не поднятие над заурядностью, но достижение консенсуса ( либо собственным эго, либо мнением социума ).
-
Если нужна эффективная реализация (с точки зрения максимальной отдачи от железа)- используйте ассемблер... меня раздражает другое какого хрена тыкают в реализацию даже в том случае , когда исходная задача не формулируется в низкоуровневых моделях.. лично я вижу одно обьяснение этому - тяжелое наследие прошлого...
Значит все игры и ресурсоёмкие приложения, включая браузеры, нужно писать на ассемблере? Вы попишите немного, поотлаживайте. Потом скажите, какой там коэффициент полезного действия и при чём тут ассемблер, если С++ включает в себя максимальное число возможностей, присутствующих исключительно с целью контроля эффективности.
Замечу, что обсуждаемые тут ссылки не являются по сути таковой возможностью, точнее ссылки они в основном не про эффективность, а про целостность и удобство (не синтаксическое удобство - лично я не вижу никакой разницы между написанием a.foo и a->foo, собственно я несколько лет на плюсах писа без ссылок вообще, и не было проблем ни с производительностью (хотя приложения были именно что про производительность) ни с синтаксисом проблем не было - авторазименовывание нафиг не нужно. А вот с нулевыми указателями как раз проблемы были :-) ).
C++ язык действительно высокого уровня. То есть он уровнем выше той же java. Это выражается в том, что в нем возможно создание собственных абстракций, так сказать, высшего порядка. В частности в C++ можно при желании создать конструкции для полноценного паттерн-матчинга.
И именно в сочетании такой вот высокоуровневости с возможностью перекладывать битики с байтиками, и состоит прелесть С++.
-
Замечу, что обсуждаемые тут ссылки не являются по сути таковой возможностью, точнее ссылки они в основном не про эффективность, а про целостность и удобство (не синтаксическое удобство ...
В том то и дело, что не смотря на понимание этого , даже вы спустились до уровня конкретной реализации и "эффективности".. про форумы си- шников я вообще молчу...(чтобы не писать кипятком)...
-
а про целостность и удобство
Если уж возвращаться к оригинальному заголовку темы, то меня интересовал лишь один вопрос: почему ссылки нельзя изменять? Выяснилось, что физически компиляторы изменение поддерживают, а запрет возник из-за прихоти создателя языка и не имеет под собой никаких оснований.
После этого поступили возражения по поводу того, что ссылки — это псевдонимы и что их в принципе менять нельзя, что, конечно же, неправда. Именно поэтому я был вынужден спуститься ниже и напомнить, что ссылки всё же указатели на физическом уровне, а возможность установки единожды используется компилятором исключительно для оптимизаций. Пример такой оптимизации локальных ссылок Вы и привели, Алексей.
Я просто прошу взглянуть свежим и трезвым взглядом на проблему. Правило одного присваивания — это сфера функциональных языков, не имеющая смысла в императивных. То есть если X — это ссылка на объект типа файловой системы, то никакой причины запретить динамически подменять реализацию файловой системы нет.
Я даже скажу более, ссылка — не псевдоним с логической точки зрения.
vector<int> &a = rand() % 100 >= 50 ? b : c;
Это чистой воды ветвление. Почему же нельзя использовать if/else?
Ну и напоследок. Изменение ссылок не влияет на их свойство быть не NULL (хрупкое, конечно, но свойство).
Кстати, те же итераторы сделаны через указатели. Не сложно вызвать какой-нибудь find на пустой коллекции, разыменовать его и привязать объект к ссылке. Будет чистой воды NULL-ссылка в самом что ни на есть корректном коде.
P.S. Нужно вместо UB использовать PB — предсказуемое поведение в реальных реализациях :)
-
Если уж возвращаться к оригинальному заголовку темы, то меня интересовал лишь один вопрос: почему ссылки нельзя изменять? Выяснилось, что физически компиляторы изменение поддерживают, а запрет возник из-за прихоти создателя языка и не имеет под собой никаких оснований.
Имхо : плохой подход... это все равно что в жавке.. отвечая на вопрос почему строки иммутабельные.. вы лезете в реализацию оных... - нет, ответ на этот вопрос - определение яп.. - просто по тому, что так ПОСТАНОВИЛИ , что жабостроки иммутабельные... - это заложено в язык (а какими низкоуровневыми механизмами это будет обеспечиваться - пофиг - разумеется, если язык их не определяет )
-
а про целостность и удобство
Если уж возвращаться к оригинальному заголовку темы, то меня интересовал лишь один вопрос: почему ссылки нельзя изменять? Выяснилось, что физически компиляторы изменение поддерживают, а запрет возник из-за прихоти создателя языка и не имеет под собой никаких оснований.
Очевидно, что это не так.
Во-первых: я явно привел пример когда видно, что внутренняя реализация ссылок и указателей может быть различна - в случае gcc у них разные UB в случае const_cast'a.
Во-вторых: аргументы влада.
В-третьих: ссылка гарантирует что переменная валидна. Никаких нулей.
После этого поступили возражения по поводу того, что ссылки — это псевдонимы и что их в принципе менять нельзя, что, конечно же, неправда. Именно поэтому я был вынужден спуститься ниже и напомнить, что ссылки всё же указатели на физическом уровне, а возможность установки единожды используется компилятором исключительно для оптимизаций. Пример такой оптимизации локальных ссылок Вы и привели, Алексей.
Я не очень понимаю различие между реализацией и, гм, оптимизацией реализации :-) В принципе полагаю возможно как минимум часть семантики (если не всю семантику) ссылок реализовать через простое копирование объекта (без вызовов конструкторов, тупо memcpy). И это не будет противоречить стандарту.
Я просто прошу взглянуть свежим и трезвым взглядом на проблему. Правило одного присваивания — это сфера функциональных языков, не имеющая смысла в императивных.
Эмм.. Что за домыслы? Иммутабельность естественно имеет смысл в императивных языках. Функциональность довольно таки ортогональна иммутабельности :-) Впрочем, задам ка я вопрос - какое основное отличие функционального языка от императивного? Вот прям основное-основное.
То есть если X — это ссылка на объект типа файловой системы, то никакой причины запретить динамически подменять реализацию файловой системы нет.
Э? При чем тут реализация?
Я даже скажу более, ссылка — не псевдоним с логической точки зрения.
vector<int> &a = rand() % 100 >= 50 ? b : c;
Это чистой воды ветвление. Почему же нельзя использовать if/else?
Конечно это ветвление. И конечно же это не отменяет тот факт, что "a" тут является псевдонимом как и обычно :-) some& foo = expr; -- тут foo является псевдонимом для результата выражения expr. const int& foo = 42; -- частный случай этого. Тут просто выражение тривиально. Кто сказал что псевдонимы могут быть только детерминированными? :-)
Тернарный оператор - это выражение, так что не вижу никаких противоречий. А вот if..else - это зачем-то statement. Поэтому увы-с, не взлетит.
Ну и напоследок. Изменение ссылок не влияет на их свойство быть не NULL (хрупкое, конечно, но свойство).
Покажи пожалуйста, ну, например на примере java, как ты собираешься бороться с нулями в изменяемых ссылках. То есть чтобы гарантированно, и не зависило от кривости рук программера.
Ну, например на том же примере твоем: vector<int> &a = rand() % 100 >= 50 ? b : c;
Пусть тут будет: 1) if..else. 2) не будет лишних инициализаций.
Кстати, те же итераторы сделаны через указатели. Не сложно вызвать какой-нибудь find на пустой коллекции, разыменовать его и привязать объект к ссылке. Будет чистой воды NULL-ссылка в самом что ни на есть корректном коде.
Нет, итератеры не сделаны через указатели :-) И пусть тебя не смущает разименовывание и стрелочки для доступа к потрохам - это обычные объекты :-) Каждый указатель можно трактовать как итератор, но не каждый этератор является указателем. Вообще, с точки зрения С++, указатели - это такие кривые и недоделанные, кастрированные, итераторы.
А то что ты описал - не будет NULL-ссылкой. Будет UB чистой воды (причем по двум разным причинам одновременно). И нет, это не корректный код. :-)
-
Во-первых: я явно привел пример когда видно, что внутренняя реализация ссылок и указателей может быть различна - в случае gcc у них разные UB в случае const_cast'a.
Гыыы... вот до чего можно дойти изучая С++ (разные UB - однако - вдумайтесь )- ;D
-
Во-первых: я явно привел пример когда видно, что внутренняя реализация ссылок и указателей может быть различна - в случае gcc у них разные UB в случае const_cast'a.
Гыыы... вот до чего можно дойти изучая С++ (разные UB - однако - вдумайтесь )- ;D
Ну, а чего удивительного? Если сущность едина, то наверно UB должно как-то одинаково быть. Хотя конечно не фа-акт. :-) Впрочем UB это не единственный вид забавных всяких B. Щща отдельную тему под это дело заведу.
-
Если сущность едина, то наверно UB должно как-то одинаково быть.
Едино только понятие UB, а кто кому чего должен - без понятия... :)
-
Кстати, те же итераторы сделаны через указатели. Не сложно вызвать какой-нибудь find на пустой коллекции, разыменовать его и привязать объект к ссылке. Будет чистой воды NULL-ссылка в самом что ни на есть корректном коде.
Разыменование нулевого указателя - это невалидный код. Уже много раз написали. Что там будет после этого кода (нулевая ссылка) - уже неинтересно. А вообще find на пустой коллекции вернет не NULL, а end() - который скорее всего будет совсем не NULL и разыменование такого итератора скорее всего обрушит дебаг сборку.
Ненулевость ссылок чисто декларативное свойство, а не средство отладки. Представь, что ты написал библиотеку с паблик функцией:
void f(params const* p)
{
assert(p);
...
}
И в документации написал, что в функцию f() передаются параметры, которые не могут быть NULL. Пользователь твоей библиотеки не читает документацию, и передается туда NULL в надежде, что функция может работать с какими-то дефолтовыми параметрами. Все крэшится, нерадивый пользователь лезет читать документацию, передает валидный указатель, компилирует, запускает...
Теперь представь, что ты изначально написал:
void f(params const& p)
{
// assert теперь не нужен, если тебе пришла нулевая ссылка, то значит в вызывающем коде есть UB, твой код абсолютно корректный
...
}
Пользователь твоей библиотеки по-прежнему не читает документацию, но NULL в твою функцию уже не отдаст (если пишет программы без UB). Все в выигрыше - тебе меньше ассертов писать и документации, пользователю меньше читать документации.
-
Кстати, те же итераторы сделаны через указатели. Не сложно вызвать какой-нибудь find на пустой коллекции, разыменовать его и привязать объект к ссылке. Будет чистой воды NULL-ссылка в самом что ни на есть корректном коде.
Разыменование нулевого указателя - это невалидный код. Уже много раз написали. Что там будет после этого кода (нулевая ссылка) - уже неинтересно. А вообще find на пустой коллекции вернет не NULL, а end() - который скорее всего будет совсем не NULL и разыменование такого итератора скорее всего обрушит дебаг сборку.
Чуть поясню:
end() вообще нельзя разименовывать (по стандарту), это раз. Его разименовывание это UB.
Два: "end() returns an iterator which is the past-the-end value for the container", даже если у нас итератор в данном конкретном случае вдруг является указателем (что бывает например в случае массивов), то элемент следующий за последним явно будет иметь не нулевой адрес, не nullptr совсем.
Три - чтобы запустить find на "пустой коллекции" нужно получить вначале начальный и конечный
итератор. Если мы итератор начальный получаем через begin(), то begin() на пустом контейнере вернет end() (согласно пункту 7 на стр 541: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1905.pdf).
Соответственно имеем find(end(vec),end(vec)). Очевидно, что контейнер ака скажем вектор, вовсе не обязан быть пустой :-)
-
просто по тому, что так ПОСТАНОВИЛИ
Именно так. Создатель языка взял идею из Алгола и урезал по субъективным причинам.
Исправление могло выглядеть как "let reference = new_object;"
Во-первых: я явно привел пример когда видно, что внутренняя реализация ссылок и указателей может быть различна - в случае gcc у них разные UB в случае const_cast'a.
Во-вторых: аргументы влада.
В-третьих: ссылка гарантирует что переменная валидна. Никаких нулей.
Алексей, смотрите. Использование указателя может транслироваться в:
1) Ничего. Оптимизация при известном адресе значения.
2) Копию константного объекта в виде временной переменной.
3) В полях структур, в сложных выражениях, в результатах невстроенныхфункций — в указатель.
А если проще: без указателей реализовать поддержку ссылок нельзя. Без оптимизаций, сугубо на указателях можно. С этим-то все согласны?
Если разрешить переустанавливать ссылки, то 3-й пункт будет использоваться чуток чаще. При чём анализ потока управления происходит и сейчас. При совершении прыжка назад (goto) ссылка изменяется, а следовательно компилятор ничего не оптимизирует и в лоб выделяет место под неё.
Для гарантии не-NULL нужны проверки времени исполнения, иначе это не гарантии.
Я не очень понимаю различие между реализацией и, гм, оптимизацией реализации :-)
Оптимизация в стандарте не прописана, исходите из предположения её полного отсутствия.
В принципе полагаю возможно как минимум часть семантики (если не всю семантику) ссылок реализовать через простое копирование объекта (без вызовов конструкторов, тупо memcpy). И это не будет противоречить стандарту.
В лоб нельзя. Просто пара причин даже для этого ограниченного случая: goto разрешён (нужно проверять, устанавливается ли ссылка один раз), структурам и стековым кадрам нельзя жиреть (не будете же вы копировать по 5 КБ на объект), объявление указателей/ссылок возможно до полного определения структур. Поправьте, если где ошибаюсь.
Эмм.. Что за домыслы? Иммутабельность естественно имеет смысл в императивных языках. Функциональность довольно таки ортогональна иммутабельности :-) Впрочем, задам ка я вопрос - какое основное отличие функционального языка от императивного? Вот прям основное-основное.
Исправляюсь. Императивным языкам не свойственна навязанная неизменность. То есть дело не в том, чтобы объявлять саму ссылку const или нет, а в том, что она всегда константная. Возьмите указатели и примените к ним то же решение: указатели всегда константные. Что такое решение даёт программисту?
Э? При чем тут реализация?
MemoryFS mem_fs;
FileFS file_fs;
AnyFS &fs = file_fs;
...
if (some condition) let fs = file_fs; // такой "фичи" нет
Кто сказал что псевдонимы могут быть только детерминированными? :-)
Для меня есть два понятия: указатель на динамическую величину или псевдоним статической. То есть в случае псевдонима мы ещё на этапе компиляции знаем, к чему привязываемся. Но это субъективное восприятие, на дискуссии не настаиваю, да и ценность этого восприятия мала.
Покажи пожалуйста, ну, например на примере java, как ты собираешься бороться с нулями в изменяемых ссылках. То есть чтобы гарантированно, и не зависило от кривости рук программера.
Ну, например на том же примере твоем: vector<int> &a = rand() % 100 >= 50 ? b : c;
Пусть тут будет: 1) if..else. 2) не будет лишних инициализаций.
А мы не отменяем необходимость инициализации при объявлении.
Object &a = b;
...
let a = c;
...
let a = d;
где let — установка указателя, а не изменение значения объекта, на которого ссылаются.
Нет, итератеры не сделаны через указатели :-) И пусть тебя не смущает разименовывание и стрелочки для доступа к потрохам - это обычные объекты :-) Каждый указатель можно трактовать как итератор, но не каждый этератор является указателем. Вообще, с точки зрения С++, указатели - это такие кривые и недоделанные, кастрированные, итераторы.
Да, речь идёт о контейнерах со сплошным хранением элементов. И здесь я имел в виду физическую реализацию, когда итератор является структурой, содержащей один указатель и нет проверок времени исполнения на выходы за границы контейнера.
Не редко вижу в ответах фразы "In fact, vectors iterators are usually implemented as pointers.", но не проверял итераторы в исходниках STL. Хотя более чем уверен, либо просто указатель, либо структура из одного поля с набором требуемых inline-операторов.
А то что ты описал - не будет NULL-ссылкой. Будет UB чистой воды (причем по двум разным причинам одновременно). И нет, это не корректный код. :-)
Неопределённое поведение в стандарте не равно неопределённому поведению в реальности. В моей реальности программисты ошибаются, программы не идеальны, а некорректные ссылки не рушат программы до попытки обращения к объектам.
Ненулевость ссылок чисто декларативное свойство, а не средство отладки. Представь, что ты написал библиотеку с паблик функцией:
Согласен. Но я ни разу не предлагал разрешить ссылкам быть нулевыми. Я предлагал вернуть им возможность указывать на разные существующие объекты.
end() вообще нельзя разименовывать (по стандарту), это раз. Его разименовывание это UB.
Знаю. Но поскольку «нельзя» относится к тем же нельзя, что и «нельзя писать несовершенный код», то в реальности где-то будет опущена проверка результата.
то элемент следующий за последним явно будет иметь не нулевой адрес, не nullptr совсем.
begin() + size() он будет. begin() вернёт nullptr, а size() будет 0.
Три - чтобы запустить find на "пустой коллекции" нужно получить вначале начальный и конечный
итератор.
Ну да, будет возвращён end(), который есть nullptr.
-
begin() + size() он будет. begin() вернёт nullptr, а size() будет 0.
Три - чтобы запустить find на "пустой коллекции" нужно получить вначале начальный и конечный
итератор.
Ну да, будет возвращён end(), который есть nullptr.
А если подумать головой? :-)
#include <iostream>
#include <vector>
int main() {
std::vector<int> a;
a.reserve(42);
std::cout << "is empty: " << a.empty() << std::endl;
std::cout << "begin: " << (size_t)&*a.begin()
<< "\tend: " << (size_t)&*a.end() << std::endl;
return 0;
}
Вывод:
is empty: 1
begin: 4388292592 end: 4388292592
Контейнер пуст, но при этом begin и end не нуль.
PS. Кстати, итераторы не являются указателями, поэтому пришлось писать такое, чтобы получить адрес: (size_t)&*a.begin()
PPS. И кажется я писал, что для твоего примера не нужно пустого контейнера, достаточно в find скормить два end'а произвольного контейнера. find'у вообще пофиг на контейнер.
-
Зачем жульничаете?
is empty: 1
begin: 0 end: 0
std::vector<int> a;
std::cout << "is empty: " << a.empty() << std::endl;
std::cout << "begin: " << (size_t)&*a.begin()
<< "\tend: " << (size_t)&*a.end() << std::endl;
И там именно указатели на выделенную память в ваши 42 элемента. Уберите reserve и увидите то, о чём я писал.
-
Зачем жульничаете?
is empty: 1
begin: 0 end: 0
std::vector<int> a;
std::cout << "is empty: " << a.empty() << std::endl;
std::cout << "begin: " << (size_t)&*a.begin()
<< "\tend: " << (size_t)&*a.end() << std::endl;
И там именно указатели на выделенную память в ваши 42 элемента. Уберите reserve и увидите то, о чём я писал.
Там нет ни одного элемента - empty дает true, это железно означает что в векторе нет НИ ОДНОГО элемента. А то что оно там что-то закешировало - это дело десятое. То же самое может получиться после серии вставок и удаления элементов.
Впрочем, оставим вектор в покое. Возьмем map:
#include <iostream>
#include <map>
int main() {
std::map<int,int> m;
std::cout << "is empty: " << m.empty() << std::endl;
std::cout << "begin: " << (size_t)&*m.begin()
<< "\tend: " << (size_t)&*m.end() << std::endl;
return 0;
}
is empty: 1
begin: 140734916160464 end: 140734916160464
Так что там про нулевость end'а в случае пустого контейнера?
-
Список:
#include <iostream>
#include <list>
int main() {
std::list<int> m;
std::cout << "is empty: " << m.empty() << std::endl;
std::cout << "begin: " << (size_t)&*m.begin()
<< "\tend: " << (size_t)&*m.end() << std::endl;
return 0;
}
is empty: 1
begin: 140734989089752 end: 140734989089752
PS. Ощущаю себя прям таки разрушителем легенд :-)
-
PS. Ощущаю себя прям таки разрушителем легенд :-)
я не улавливаю другое... зачем Берсеркеру разименовывать итераторы... а х ..да едрить.. соображения "эффективности"...
-
PS. Ощущаю себя прям таки разрушителем легенд :-)
я не улавливаю другое... зачем Берсеркеру разименовывать итераторы... а х ..да едрить.. соображения "эффективности"...
Не-не-не, разименовывание итератора дает тебе собственно объектик который соответствует данному итератору (но естественно не дает тебе собственно тушку самого итератора, тушка итератора - это итератор и есть). Это абсолютно нормально и правильно.
При этом итераторы почти никогда не бывают сырыми указателями, поэтому итератор соответствующий nullptr - это что-то довольно дикое в общем случае.
-
И снова жульничаете. Я же сказал, для контейнеров со сплошным блоком памяти. Списки определяются как классические двусвязные. Вы видите на консоли адрес на стеке (это много хуже, чем NULL, кстати).
Вот код для вектора из STL:
// iterators
/**
* Returns a read/write iterator that points to the first
* element in the %vector. Iteration is done in ordinary
* element order.
*/
iterator
begin() _GLIBCXX_NOEXCEPT
{ return iterator(this->_M_impl._M_start); }
/**
* Returns a read-only (constant) iterator that points to the
* first element in the %vector. Iteration is done in ordinary
* element order.
*/
const_iterator
begin() const _GLIBCXX_NOEXCEPT
{ return const_iterator(this->_M_impl._M_start); }
/**
* Returns a read/write iterator that points one past the last
* element in the %vector. Iteration is done in ordinary
* element order.
*/
iterator
end() _GLIBCXX_NOEXCEPT
{ return iterator(this->_M_impl._M_finish); }
Для списка:
// iterators
/**
* Returns a read/write iterator that points to the first element in the
* %list. Iteration is done in ordinary element order.
*/
iterator
begin() _GLIBCXX_NOEXCEPT
{ return iterator(this->_M_impl._M_node._M_next); }
/**
* Returns a read-only (constant) iterator that points to the
* first element in the %list. Iteration is done in ordinary
* element order.
*/
const_iterator
begin() const _GLIBCXX_NOEXCEPT
{ return const_iterator(this->_M_impl._M_node._M_next); }
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
я не улавливаю другое... зачем Берсеркеру разименовывать итераторы... а х ..да едрить.. соображения "эффективности"...
Разыменование является нормальным явлением.
-
Разыменование является нормальным явлением.
и итераторов в частности?
-
И снова жульничаете. Я же сказал, для контейнеров со сплошным блоком памяти. Списки определяются как классические двусвязные. Вы видите на консоли адрес на стеке (это много хуже, чем NULL, кстати).
Не понял, где ты что-то писал про сплошные блоки памяти?
Кстати, те же итераторы сделаны через указатели. Не сложно вызвать какой-нибудь find на пустой коллекции, разыменовать его и привязать объект к ссылке. Будет чистой воды NULL-ссылка в самом что ни на есть корректном коде.
Итого ограничения: пустой контейнер (то есть empty() выдает true), и вызов для него find. Пустой map, list и vector (у которого что-то там унутре зарезервировано) -- вполне подходят.
PS. А адрес чего именно вижу - стека, или кучи, или вообще статической памяти - глубоко пофиг до тех пор, пока реализация контейнера соответствует стандарту. Ничто не мешает написать реализацию vector'а которая будет в некоторых случаях располагать свои элементы на стеке, более того, вектор и вообще стандартная либа может быть ВШИТА в компилятор, и соответственно реализовываться средствами самого компилятора, не имея исходников как таковых.
-
лично я вижу здесь одно - Берсеркер пытается хакнуть stl... вне зависимости от результата, имхо, не джедайское это дело...
-
Разыменование является нормальным явлением.
и итераторов в частности?
Да, это естественный путь достать объект из контейнера. Других путей, в общем случае, пожалуй и нет.
(разименовывание итератора -- это не разименовывание указателя! это высокоуровневая операция!)
-
Разыменование является нормальным явлением.
и итераторов в частности?
Да, это естественный путь достать объект из контейнера. Других путей, в общем случае, пожалуй и нет.
(разименовывание итератора -- это не разименовывание указателя! это высокоуровневая операция!)
я имею ввиду ручное разименование?
-
Разыменование является нормальным явлением.
и итераторов в частности?
Да, это естественный путь достать объект из контейнера. Других путей, в общем случае, пожалуй и нет.
(разименовывание итератора -- это не разименовывание указателя! это высокоуровневая операция!)
я имею ввиду ручное разименование?
Кажется я запутался в твоей путанице терминологий :-)
Давай на примере:
list<int> some_list;
i = find(some_list.begin(), some_list.end(),42);
if (i!=some_list.end()) cout << *i;
Такое - абсолютно корректно. Тут i - это ни разу не указатель.
-
Не понял, где ты что-то писал про сплошные блоки памяти?
Нет, итератеры не сделаны через указатели :-) И пусть тебя не смущает разименовывание и стрелочки для доступа к потрохам - это обычные объекты :-) Каждый указатель можно трактовать как итератор, но не каждый этератор является указателем. Вообще, с точки зрения С++, указатели - это такие кривые и недоделанные, кастрированные, итераторы.
Да, речь идёт о контейнерах со сплошным хранением элементов. И здесь я имел в виду физическую реализацию, когда итератор является структурой, содержащей один указатель и нет проверок времени исполнения на выходы за границы контейнера.
Не редко вижу в ответах фразы "In fact, vectors iterators are usually implemented as pointers.", но не проверял итераторы в исходниках STL. Хотя более чем уверен, либо просто указатель, либо структура из одного поля с набором требуемых inline-операторов.
Итого ограничения: пустой контейнер (то есть empty() выдает true), и вызов для него find. Пустой map, list и vector (у которого что-то там унутре зарезервировано) -- вполне подходят.
Буду краток.
Нет, итератеры не сделаны через указатели :-)
Там, где возможно, сделаны. Например, в векторах. В случае списков возвращается адрес узла, у которого переопределена операция (*). Но вот незадача, содержимое элемента хранится в самом узле, а значит, после того, как end() вернул мусор, мы получили ссылку на мусор. Ссылки на мусор гораздо разрушительнее по последствиям, но это не являлось темой обсуждения. Я просто показал, насколько всё низкоуровнево внутри ради достижения эффективности.
По поводу возможности встраивания STL в сам язык и других предложений — это не ко мне, а к энтузиастам-разработчиком и гипотетическим будущим реализаторам STL.
-
Там, где возможно, сделаны. Например, в векторах. В случае списков возвращается адрес узла, у которого переопределена операция (*). Но вот незадача, содержимое элемента хранится в самом узле, а значит, после того, как end() вернул мусор, мы получили ссылку на мусор. Ссылки на мусор гораздо разрушительнее по последствиям, но это не являлось темой обсуждения.
Это зависит от реализации же! Контейнеры вполне могут резирвировать для end'а объект-заглушку. Вот вообще как нефиг делать (более того, подозреваю что в дебажном коде это так и делается, например в мелкософфтоффской реализации stl'я).
Еще раз - что там как унутре хранится в конкретной реализации может быть интересно ну только в очень специфических случаях. Полагаться можно только на то, что прописано в стандарте. Все. Точка.
Изучение языка ковыряясь в байтиках сгенерированных конкретной реализацией не даст понимания языка, в лучем случае даст понимание как иногда работает данная конкретная реализация языка (но при выходе следующей версии этой реализации программы могут просто перестать работать!).
Да.. А потом к нам приходят на собеседование люди с резюме, где в графе "языки программирования" гордо красуется нечто вроде: VisualC++. Но бывает конечно хуже - в графе написано C++, а знает он только Microsoft Visual C++ 2005 под x86 (32 бита). При этом языка не знает, и писать код так чтобы он был без UB (то есть работал всегда и везде, с любой валидной реализацией stl, компилятора и так далее) не умеет. Пичаль...
Я просто показал, насколько всё низкоуровнево внутри ради достижения эффективности.
Весь stl написан крайне высокоуровнево, но при этом он близок к железу. Следует одно от другого таки отличать :-)
По поводу возможности встраивания STL в сам язык и других предложений — это не ко мне, а к энтузиастам-разработчиком и гипотетическим будущим реализаторам STL.
Я к тому, что это явно прописано в стандарте, то есть возможность такого.
-
Такое - абсолютно корректно. Тут i - это ни разу не указатель.
не понял, в общем случае операция разименования перекрывается или нет?
-
Такое - абсолютно корректно. Тут i - это ни разу не указатель.
не понял, в общем случае операция разименования перекрывается или нет?
Да. Можно написать свой класс который будет иметь операцию разименовывания.
Выглядит это как-то так:
struct Foo {
int operator*(){return 42;}
};
Естественно оператор разименовывания не обязан возвращать именно int.
-
Ну вот, Алексей, мы и пришли к заключению. Вы, вероятно, знаете множество реализаций STL для C++. Мой опыт гораздо более скромный. И это скромный опыт говорит, что в STL ради оптимизации везде, где можно, в качестве итераторов используются указатели. Это первое. И второе: нет множества проверок времени исполнения, которые бы гарантировали безопасность контейнеров как абстрактных типов данных. Вместо этого там грабли и грабли.
Я не вижу ничего плохого в том, чтобы знать, где могут быть причины ошибок. Понимание наиболее вероятного варианта реализации позволяет как самому создавать новые реализации, так и ориентироваться в существующих, ну и, разумеется, при отладке. А если на работу не возьмёте, так уж и быть, не обижусь ;)
-
Ну вот, Алексей, мы и пришли к заключению. Вы, вероятно, знаете множество реализаций STL для C++. Мой опыт гораздо более скромный. И это скромный опыт говорит, что в STL ради оптимизации везде, где можно, в качестве итераторов используются указатели. Это первое. И второе: нет множества проверок времени исполнения, которые бы гарантировали безопасность контейнеров как абстрактных типов данных. Вместо этого там грабли и грабли.
Ну, кое-что все же есть. Например at() вместо [] для вектора. И будет вам проверка выхода за границы дозволенного :-)
Ну, кроме того, в каждой конкретной реализации обычно есть волшебные ключики которые врубают рантаймовые проверки всего чего можно и нельзя. Ну тормозит (относительно безключевого), зато безопасно и столь же "быстро" как в языках с проверками.
Ну и наконец - никто не мешает нарисовать удобную для тебя обертку над всем этим, которая сама будет проверять на end().
Я не вижу ничего плохого в том, чтобы знать, где могут быть причины ошибок. Понимание наиболее вероятного варианта реализации позволяет как самому создавать новые реализации, так и ориентироваться в существующих, ну и, разумеется, при отладке. А если на работу не возьмёте, так уж и быть, не обижусь ;)
А я на работе на С++ и не пишу практически, так что С++'сник мне никак не нужен :-) Вот от жабоскриптоведа/html-профи я бы не отказался :-) Ну и есть задачи на Go и матлабе, а также на ObjC&C99.
А для того, чтобы знать какие могут быть причины ошибок - нужно таки стандарт (ну или хотя бы более человеческое описание на cppreference.com да www.cplusplus.com/reference/) читать - там написано. Из реализации эти знания выколупывать - занятие не благодарное абсолютно (да еще и ответ может быть не верным).
-
Читаю всё вместе: критику языка (лучшее в сети — Defective C++ или C++ FQA), cppreference, ответы на stackoverflow ну и отладчик с исходниками STL забывать не стоит. Так сказать, для полноты картины 8)
Однако когда речь идёт о коде, я всегда предпочитаю представлять перед собой то, что стоит за абстракциями. Иногда вплоть до команд. Судя по тому, как пишутся чувствительные к производительности вещи, не только у меня подобное восприятие.
-
Читаю всё вместе: критику языка (лучшее в сети — Defective C++ или C++ FQA), cppreference, ответы на stackoverflow ну и отладчик с исходниками STL забывать не стоит. Так сказать, для полноты картины 8)
Однако когда речь идёт о коде, я всегда предпочитаю представлять перед собой то, что стоит за абстракциями. Иногда вплоть до команд. Судя по тому, как пишутся чувствительные к производительности вещи, не только у меня подобное восприятие.
Чувствительные к производительности вещи обычно таки имеют довольно узкую нишу в плане железа (иначе нужной производительности не достичь) и там свой подход. Вплоть до выкидывания текущей реализации stl и написания своего велосипеда под особенности задачи, железа, компилятора и операционки.
Ну, например если у меня задача такова, что мне кроме линукса и gcc на нем никто не нужен, да к тому же задача требует множества конкурентных нитей, то я вполне могу заюзать бесконечный растущий системный стек в виде двусвязного списка чанков для каждой из нити и получить с этого немалый профит.
Но я буду четко осознавать (и напишу большими буквами в нескольких местах кода и доки), что это решение будет работать только вот в этих вот условиях. В остальных случаях будет кака-бяка и пичаль.
Ну, то есть хорошо бы неплохо уже знать сам язык чтобы легко видеть границу между стандартом на язык (что гарантирует что оно будет вот так работать, не работать или вообще UB везде), реализацией языка, фичами которые навешиваются на язык конкретными реализациями вопреки стандарту, и условиями которые определяет среда исполнения (операционка, железка и так далее).
PS. А еще есть http://isocpp.org/
-
Такое - абсолютно корректно. Тут i - это ни разу не указатель.
не понял, в общем случае операция разименования перекрывается или нет?
Да. Можно написать свой класс который будет иметь операцию разименовывания.
Выглядит это как-то так:
struct Foo {
int operator*(){return 42;}
};
Естественно оператор разименовывания не обязан возвращать именно int.
как переопределяются операторы я знаю.. меня интересуют другой вопрос.. - в самой stl - перекрывается оператор разименования или это дело исключительно обьекта который помещается в контейнер.
-
Читаю всё вместе: критику языка (лучшее в сети — Defective C++ или C++ FQA), cppreference, ответы на stackoverflow ну и отладчик с исходниками STL забывать не стоит. Так сказать, для полноты картины 8)
Однако когда речь идёт о коде, я всегда предпочитаю представлять перед собой то, что стоит за абстракциями. Иногда вплоть до команд. Судя по тому, как пишутся чувствительные к производительности вещи, не только у меня подобное восприятие.
желание хакать неистребимо... гораздо труднее приучить себя писать инвариантно относительно особенностей реализации.. - ибо, на первый взгляд , это всегда 100% оверхед и потери в производительности, кроме того всегда приятно переложить часть ответственности на создателей копмилятора и яп (дескать буду использовать адресную арифметику и писать хитрожопые выражения с инкрементальными операторами ... и все будет за..сь).. дескать они люди заведомо более вумные , коль скоро создали такую похерень, как С++... -так что единомышленниками вы , Berserker, обделены никогда не будете... :).
-
Такое - абсолютно корректно. Тут i - это ни разу не указатель.
не понял, в общем случае операция разименования перекрывается или нет?
Да. Можно написать свой класс который будет иметь операцию разименовывания.
Выглядит это как-то так:
struct Foo {
int operator*(){return 42;}
};
Естественно оператор разименовывания не обязан возвращать именно int.
как переопределяются операторы я знаю.. меня интересуют другой вопрос.. - в самой stl - перекрывается оператор разименования или это дело исключительно обьекта который помещается в контейнер.
В stl у каждого типа итераторов перекрыт оператор разименовывания - итератор это отдельный, обобщенный тип (struct обычно), не завязанный вообще говоря, на тип того что там в контейнере будет храниться.
Перекрыто что-то у тех кто будет в контейнерах храниться или нет - никого не волнует.
-
В stl у каждого типа итераторов перекрыт оператор разименовывания - итератор это отдельный, обобщенный тип (struct обычно), не завязанный вообще говоря, на тип того что там в контейнере будет храниться.
Перекрыто что-то у тех кто будет в контейнерах храниться или нет - никого не волнует.
спасибо, это исчерпывающий ответ, ставящий крест на рассуждениях Берсеркера. Лично я stl не застал -96-98 годах когда приходилось использовать С++, фиг знает почему... отсюда и "странные" вопросы, в особенности после того как осознал, что сам язык С++ довольно сильно изменился. :D
-
спасибо, это исчерпывающий ответ, ставящий крест на рассуждениях Берсеркера.
Поясните, если не трудно. Я приводил исключительно факты, которые может проверить каждый. Нет никакой разницы, что у итератора есть оператор разыменования, возвращающий голый указатель, в результате чего ссылка принимает мусорное значение. Частным случаем мусорного значения является нулевой адрес. Зачастую всё гораздо хуже (адрес на стеке или временный блок памяти). Помимо прочего изменение контейнера делает итераторы неверными, но снова без каких-либо проверок времени исполнения. Иначе говоря, итератор, который хранит адрес блока памяти, узла списка или элемента карты и не является устойчивым или защищённым от легального вызова методов, это дырявый итератор. И в STL такие все.
Любой кривой указатель превращается в цивильную ссылку через разыменование. А постоянные отсылки к правильному коду (valid), в котором такие случаи невозможны, из той же оперы, что и просьба не совершать ошибок вообще.
P.S. Если проще, то ссылки не имеют права быть мусорными. А значит если std::vector на запрос элемента через оператор [] по описанию возвращает ссылку, то это должна быть ссылка или выброс исключения. Если итератор стал неверным, при попытке его использования должно тоже бросаться исключение. Ну а реальности всё так же, как и с ручным управлением памяти. Не совершайте ошибок, господа.
P.S.S Два оператора доступа к элементу ([], at) — это просто шутка разработчиков. Объект либо гарантирует безопасность своего использования, либо нет. А предоставление для ограниченного функционала специальной безопасной функции с выбивающимся из общего стиля синтаксисом — это яркий пример неудачного решения.
-
Помимо прочего изменение контейнера делает итераторы неверными, но снова без каких-либо проверок времени исполнения.
Не всякого контейнера. Валидность итераторов и изменение контейнеров явно прописано в стандарте. Так что писать без UB можно, при желании ;)
Иначе говоря, итератор, который хранит адрес блока памяти, узла списка или элемента карты и не является устойчивым или защищённым от легального вызова методов, это дырявый итератор. И в STL такие все.
STL написана в расчете на эффективность (с точки зрения быстродействия). Дырявые итераторы (порой превращающиеся в голые указатели) - прямое следствие такого расчета. Вообще не вижу смысла перетирать еще раз про "безопасность vs эффективность". С++ традиционно небезопасный и быстрый. Безопасность в С++ достигается не низкоуровневыми средствами рантайма (типа GC), а высокоуровневыми под задачу (смарт поинтеры, ненулевые указатели и т.д.). При этом железных гарантий, естественно, нет (внизу голые поинтеры). Но как показывает практика при желании и высокоуровневых гарантий вполне хватает. В частности, STL - попытка подняться над поинтерами и массивами. И еще раз - без потери эффективности. Со своей задачей справляется неплохо, при умении пользоваться.
Любой кривой указатель превращается в цивильную ссылку через разыменование.
Кривой указатель не может превратиться в валидную ссылку. Что за фантазии такие? Кривой указатель - это UB и точка.
P.S. Если проще, то ссылки не имеют права быть мусорными.
Так же как и мусорные указатели не имеют права быть разименованными. И это "право" в С++ поддерживает программист, а не рантайм.
А значит если std::vector на запрос элемента через оператор [] по описанию возвращает ссылку, то это должна быть ссылка или выброс исключения.
Это опять твои фантазии. Для [] прописан UB для невалидного индекса. Что он там возвращает в таком случае уже не важно.
Если итератор стал неверным, при попытке его использования должно тоже бросаться исключение. Ну а реальности всё так же, как и с ручным управлением памяти. Не совершайте ошибок, господа.
Запили свою привильную STL. Кто мешает? Гарантировать валидность смартпоитера в С++ можно. Так же как и всегда валидный итератор написать. Такая STL будет тормознее обычной и поэтому не очень востребованной. Нафига козе баян? Если можно просто взять другой безопасный язык?
-
"безопасный язык" - штука ну о-очень относительная. Вот скажем java, c# не являются безопасными языками с точки зрения haskell'я :-)
А еще понятие безопасности зависит от задачи. Если у меня жесткий реалтайм, то какой-нибудь чертов Си или Асм будет более безопасен чем haskell ;-)
Впрочем, "безопасность по памяти" в C++11 включена же:
http://en.cppreference.com/w/cpp/memory/gc/pointer_safety
-
спасибо, это исчерпывающий ответ, ставящий крест на рассуждениях Берсеркера.
Поясните, если не трудно.
Да , конечно - мы друг друга не понимаем... в данном случае крест ставится на "легитимности" рассуждений (но разумеется это имхо)... тут все просто.. если разименование итератора это ОК. то ваши рассуждения были бы легитимными в том случае если бы операция разименования не перекрывалась в итераторах , а раз это не так (пусть даже если это перекрытие будет чисто декларативным - то есть по факту не наблюдаемым в коде) - ваши действия есть чистый хак (или эксплоит , если вам это больше нравится), и рассуждения могут оказаться неверными в следующей реализации STL. То есть смотрите... я говорю про то, что ваши рассуждения имеют очевидную уязвимость ВНЕ ЗАВИСИМОСТИ от того насколько они верны в приведенных вами примерах....
-
Дизер, я вовсе не полагаюсь на особенности реализации при написании кода. Так что в этом вопросе у нас разногласий нет.
Алексей, хочу заметить, что никаких волшебных опций или макросов для контроля безопасности STL нет.
Влад, скажите, Вы действительно считаете, что правильным библиотекам достаточно указать в описаниях функций и методов о неопределённом поведении (на деле старту развала программы) при неверных аргументах, и всё, проверки не нужны? Вы же сами приводили пример, когда программист не читает документацию. В чём проблема использовать указатель вместо ссылки, не ставить assert, а в документации написать про UB при nullptr?
-
Дизер, я вовсе не полагаюсь на особенности реализации при написании кода. Так что в этом вопросе у нас разногласий нет.
если это так - то зачем вообще приводить было этот пример....
-
Алексей, хочу заметить, что никаких волшебных опций или макросов для контроля безопасности STL нет.
В стандарте - нет. В конкретных реализациях - бывает.
Про контроль безопасности по памяти, я в предыдущем посте дал ссылку. Это в стандарте есть безотносительно stl.
И да, я считаю решение с [] и at() в векторе абсолютно адекватным. Неоднократно пользовался и тем и этим (и в виде смеси) и очень был доволен.
-
Размышляя на досуге, пришёл к выводу, что большинство типов указателей, как существующих, так и новых, можно описать при помощи ограниченного набора атрибутов. Набор представлен ниже:
const — указатель не изменяемый, инициализация по месту объявления или в качестве аргумента функции;
no_addr — запрещает применение операции получения адреса к указателю; Используется для повышения возможностей оптимизатора.
auto_deref — упоминание идентификатора указателя равнозначно его разыменованию на месте "(*p)". Включает в себя no_addr. Для установки значения самого указателя используется специальный синтаксис (ключевое слово let, инициализация при объявлении в С++, аргумент функции);
fragile — "ломкий". Указатель на данные подлежащие скорому удалению. Семантика перемещения в С++;
not_null — гарантированно указывает на данные, проверка времени компиляции;
null_guard — проверка на неравенство нулевому указателю во время исполнения (в аргументах функций, при присвоении указателей без атрибута null_guard или not_null).
Теперь составим известные нам типы указателей из Ады, Паскаля, Оберона и С++ (pointer — просто указатель):
1) Указатель, к которому нельзя применить операцию взятия адреса:
typedef no_addr pointer ptr;
int x;
int ptr px = &x; // px = @x
&px // error
*px = 7; // можно оптимизировать до x = 7, px выбросить вообще
2) Автоматически разыменовываемый указатель
typedef auto_deref pointer ref;
int x;
int ref px; // px = @???
px = 1; // possible run-time crash
let px = &x; // px = @x
px = 1; // x = 1
3) Гарантированно указывающий на что-либо указатель
typedef not_null pointer ptr;
int x, y;
int ptr p; // compile time error
int ptr p = &x; // p = @x
p = &y; // p = @y
*p = 1; // y = 1
4) Гарантированно ненулевой указатель, проверка времени исполнения
typedef null_guard pointer ptr;
void Test (int ptr p) {}
iTest(nullptr); // run-time error
5) Семантика перемещения — функция принимает аргументом указатель на объект, который будет следом удалён
typedef fragile pointer ptr;
void Test (TObject ptr obj) {}
int *p = new int();
Test(p);
delete p;
6) VAR-параметр Оберона, Паскаля, Ады
typedef const auto_deref not_null pointer var;
procedure Test (var x: integer);
Test(1); // compile-time error
Test(x); // ok
Test(p^); // ok if p is pointer to integer
7) not null аргумент Ады, проверка времени исполнения
typedef not_null pointer ptr;
procedure Test (x: ptr integer);
Test(nil); // compile-time error
Test(@x); // ok
Test(p); // ok
8) Классические ссылки в С++
typedef const not_null auto_deref pointer &;
Примечание: возможность иметь ссылки на константы или возвращаемые по значению объекты есть синтаксический сахар, который можно с тем же успехом применить к любым пользовательским указателям в новой модели:
const int* p = 1; // p = @c1, c1 is const int, c1 = 1
TObject Test () {}
TObject* obj = Test(); // obj = @local1, local1 is TObject, @local1 передаётся функции теневым аргументом
Что думаете, господа?
-
Что думаете, господа?
Я глубоко в написанное не вникал, поэтому что думаю сказать не могу. Зато могу посоветовать прочесть про типы "указателей" в Rust'e. Там нечто подобное вроде бы как раз и сделали.
-
Пришло время таки взглянуть на Rust.
-
Не похоже (http://static.rust-lang.org/doc/0.6/rust.html#pointer-types):
8.1.8 Pointer types
All pointers in Rust are explicit first-class values. They can be copied, stored into data structures, and returned from functions. There are four varieties of pointer in Rust:
Managed pointers (@)
These point to managed heap allocations (or "boxes") in the task-local, managed heap. Managed pointers are written @content, for example @int means a managed pointer to a managed box containing an integer. Copying a managed pointer is a "shallow" operation: it involves only copying the pointer itself (as well as any reference-count or GC-barriers required by the managed heap). Dropping a managed pointer does not necessarily release the box it points to; the lifecycles of managed boxes are subject to an unspecified garbage collection algorithm.
Owning pointers (~)
These point to owned heap allocations (or "boxes") in the shared, inter-task heap. Each owned box has a single owning pointer; pointer and pointee retain a 1:1 relationship at all times. Owning pointers are written ~content, for example ~int means an owning pointer to an owned box containing an integer. Copying an owned box is a "deep" operation: it involves allocating a new owned box and copying the contents of the old box into the new box. Releasing an owning pointer immediately releases its corresponding owned box.
Borrowed pointers (&)
These point to memory owned by some other value. Borrowed pointers arise by (automatic) conversion from owning pointers, managed pointers, or by applying the borrowing operator & to some other value, including lvalues, rvalues or temporaries. Borrowed pointers are written &content, or in some cases &f/content for some lifetime-variable f, for example &int means a borrowed pointer to an integer. Copying a borrowed pointer is a "shallow" operation: it involves only copying the pointer itself. Releasing a borrowed pointer typically has no effect on the value it points to, with the exception of temporary values, which are released when the last borrowed pointer to them is released.
Raw pointers (*)
Raw pointers are pointers without safety or liveness guarantees. Raw pointers are written *content, for example *int means a raw pointer to an integer. Copying or dropping a raw pointer is has no effect on the lifecycle of any other value. Dereferencing a raw pointer or converting it to any other pointer type is an unsafe operation. Raw pointers are generally discouraged in Rust code; they exist to support interoperability with foreign code, and writing performance-critical or low-level functions.