Здравствуйте, gegMOPO4, Вы писали:
MOP>Здравствуйте, Vain, Вы писали: V>>Речь идёт про невозможность использования связки vector+tuple<&> из-за различного поведения vector'а в зависимости от алгоритма размещения по памяти. Произошло перевыделение — вызываем конструкторы, не произошло — операторы??? А кто сказал что они одинаково работают вообще? vector? Ну так его и проблемы. MOP>Нет, это проблемы типа элементов вектора. Вы ведь не будете предъявлять претензии к вектору за то, что в нём нельзя хранить auto_ptr?
Как раз с автопоинтером поведение очевидно.
MOP>Если мы создаём объект на месте, где ничего не было — вызывается конструктор. Если на месте другого объекта — оператор присваивания. В этом их разница.
Ну так половина конструкций с вектором работать не будет, а оно нафига надо? Можно было давно уже пофиксить, к примеру, вызывать вместо оператора присваивания — конструктор копирования и деструктор.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, Vain, Вы писали: V>Здравствуйте, gegMOPO4, Вы писали: MOP>>Здравствуйте, Vain, Вы писали: V>>>Речь идёт про невозможность использования связки vector+tuple<&> из-за различного поведения vector'а в зависимости от алгоритма размещения по памяти. Произошло перевыделение — вызываем конструкторы, не произошло — операторы??? А кто сказал что они одинаково работают вообще? vector? Ну так его и проблемы. MOP>>Нет, это проблемы типа элементов вектора. Вы ведь не будете предъявлять претензии к вектору за то, что в нём нельзя хранить auto_ptr? V>Как раз с автопоинтером поведение очевидно.
Для вас это просто привычно. Потому, что жужжат об этом на каждом углу. На самом деле очевидность auto_ptr и tuple<&> совершенно одинакова. Лет через десять это станет очевидно всем.
MOP>>Если мы создаём объект на месте, где ничего не было — вызывается конструктор. Если на месте другого объекта — оператор присваивания. В этом их разница. V>Ну так половина конструкций с вектором работать не будет, а оно нафига надо? Можно было давно уже пофиксить, к примеру, вызывать вместо оператора присваивания — конструктор копирования и деструктор.
Это vector<auto_ptr<>> — половина конструкций? Только в примерах «как делать нельзя». Почему-то у тех, кто понимает, как оно работает, всё раньше прекрасно работало. И будет работать. А вот ваше предложение весьма вероятно сломает кучу кода.
Здравствуйте, gegMOPO4, Вы писали:
V>>>>Речь идёт про невозможность использования связки vector+tuple<&> из-за различного поведения vector'а в зависимости от алгоритма размещения по памяти. Произошло перевыделение — вызываем конструкторы, не произошло — операторы??? А кто сказал что они одинаково работают вообще? vector? Ну так его и проблемы. MOP>>>Нет, это проблемы типа элементов вектора. Вы ведь не будете предъявлять претензии к вектору за то, что в нём нельзя хранить auto_ptr? V>>Как раз с автопоинтером поведение очевидно. MOP>Для вас это просто привычно. Потому, что жужжат об этом на каждом углу. На самом деле очевидность auto_ptr и tuple<&> совершенно одинакова. Лет через десять это станет очевидно всем.
Как раз таки очевидность не одинакова.
MOP>>>Если мы создаём объект на месте, где ничего не было — вызывается конструктор. Если на месте другого объекта — оператор присваивания. В этом их разница. V>>Ну так половина конструкций с вектором работать не будет, а оно нафига надо? Можно было давно уже пофиксить, к примеру, вызывать вместо оператора присваивания — конструктор копирования и деструктор. MOP>Это vector<auto_ptr<>> — половина конструкций?
Ну так vector<void> тоже не входит.
MOP>Только в примерах «как делать нельзя». Почему-то у тех, кто понимает, как оно работает, всё раньше прекрасно работало. И будет работать. А вот ваше предложение весьма вероятно сломает кучу кода.
Оно даже на скомпилируется при использовании, а вот std::vector<blabla<&> > сломает.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
gegMOPO4:
MOP>Присваивание кортежу — это присваивание элементам. Для ссылки присваивание приводит к изменению значения связанного объекта.
В большинстве случаев семантика копирования для копирующих конструкторов и для копирующих операторов присваивания едина, и, в частности, некоторые функции std::vector-а полагаются на это свойство. Двойственная семантика копирования — потенциальный источник ошибок. В CWG (Core Working Group) это, похоже, хорошо понимают.
struct X
{
explicit X(int &n) : m(n) {}
int &m;
};
int main()
{
int i = 0;
X x1(i);
X x2 = x1;
x2 = x1;
}
Здесь попытка присваивания x2 = x1 должна диагностироваться. Если хочется экзотического почленного присваивания, его придётся реализовать явно.
В LWG к диагностике потенциальных и даже явных ошибок относятся весьма легкомысленно (как, в общем-то, и к документированию стандартной библиотеки), так что убеждать её представителей в целесообразности замены operator= на другую функцию, похоже, бесполезно.
MOP>В этом смысл ссылок, если вам это не нужно, используйте указатели.
А ещё лучше не использовать такие библиотечные средства вообще.
MOP>Вставка в середину вектора приводит к конструированию нового элемента за пределами старого вектора (где ничего не было) и присвоению новых значений выше места вставки (потому, что там уже были сконструированные объекты).
MOP>Мне всё понятно, всё выглядит последовательным и логичным.
В данном случае insert не выполняет ту операцию, для осуществления которой данная функция предназначена. То, что её поведение можно объяснить, вникая в детали реализации, я не считаю свидетельством "последовательности и логичности" дизайна библиотеки.
M>> При надлежащей реализации tuple и vector в случае моего примера можно добиться ожидаемого поведения программы во время выполнения или хотя бы диагностики нарушения предусловий во время компиляции.
MOP>Как? Как вы это реализуете, а главное, как обоснуете?
Во-первых, следует убрать потенциально опасный оператор присваивания, допускающий работу со ссылками, и вместо него ввести другую функцию (желание попользоваться экзотическим присваиванием будет куда более явным). Сделать это можно очень просто: достаточно удалить следующие явные объявления вместе с соответствующими определениями операторных функций:
и нужные специальные функции будут сгенерированы неявно.
Также следует запретить опасный swap при инстанцировании ссылочными типами.
Во-вторых, перемещение элементов в std::vector можно выполнять с помощью деструктора и не бросающего исключений move-конструктора (когда std::is_move_assignable<element_type>::value == false && std::is_nothrow_move_constructible<element_type>::value == true). Такой подход позволил бы расширить применимость вектора.
Здравствуйте, Masterkent, Вы писали: M>gegMOPO4: MOP>>Присваивание кортежу — это присваивание элементам. Для ссылки присваивание приводит к изменению значения связанного объекта. M>В большинстве случаев семантика копирования для копирующих конструкторов и для копирующих операторов присваивания едина, и, в частности, некоторые функции std::vector-а полагаются на это свойство. Двойственная семантика копирования — потенциальный источник ошибок. В CWG (Core Working Group) это, похоже, хорошо понимают.
Следовательно, на параметры std::vector должно накладываться ещё одно ограничение. А вернее, только на некоторые операции.
M>
struct X
M>{
M> explicit X(int &n) : m(n) {}
M> int &m;
M>};
M>int main()
M>{
M> int i = 0;
M> X x1(i);
M> X x2 = x1;
M> x2 = x1;
M>}
M>Здесь попытка присваивания x2 = x1 должна диагностироваться. Если хочется экзотического почленного присваивания, его придётся реализовать явно.
Может быть стоило и присваивание кортежей со ссылками запретить. Но, возможно, у необходимости присваивания были веские причины. Подозреваю, что присваивание кортежу со ссылками станет идиомой и одним из основных применений кортежей со ссылками.
M>В LWG к диагностике потенциальных и даже явных ошибок относятся весьма легкомысленно (как, в общем-то, и к документированию стандартной библиотеки), так что убеждать её представителей в целесообразности замены operator= на другую функцию, похоже, бесполезно.
Это нарушит совместимость.
MOP>>В этом смысл ссылок, если вам это не нужно, используйте указатели. M>А ещё лучше не использовать такие библиотечные средства вообще.
Так никто не заставляет стрелять себе в ногу. Но ружьё есть.
MOP>>Вставка в середину вектора приводит к конструированию нового элемента за пределами старого вектора (где ничего не было) и присвоению новых значений выше места вставки (потому, что там уже были сконструированные объекты). MOP>>Мне всё понятно, всё выглядит последовательным и логичным. M>В данном случае insert не выполняет ту операцию, для осуществления которой данная функция предназначена. То, что её поведение можно объяснить, вникая в детали реализации, я не считаю свидетельством "последовательности и логичности" дизайна библиотеки.
А я считаю, что кортежи со ссылками для этой операции нарушают некоторый контракт, который должен быть явно описан.
M>>> При надлежащей реализации tuple и vector в случае моего примера можно добиться ожидаемого поведения программы во время выполнения или хотя бы диагностики нарушения предусловий во время компиляции. MOP>>Как? Как вы это реализуете, а главное, как обоснуете? M>Во-первых, следует убрать потенциально опасный оператор присваивания, допускающий работу со ссылками, и вместо него ввести другую функцию (желание попользоваться экзотическим присваиванием будет куда более явным). Сделать это можно очень просто: достаточно удалить следующие явные объявления вместе с соответствующими определениями операторных функций: M>
M>и нужные специальные функции будут сгенерированы неявно.
А как вы обеспечите std::copy в контейнер с кортежами ссылок? Изменение объектов, на которые ссылаются, а не переназначение ссылок является требуемым поведением. Иначе бы использовали указатели, а не ссылки.
M>Во-вторых, перемещение элементов в std::vector можно выполнять с помощью деструктора и не бросающего исключений move-конструктора (когда std::is_move_assignable<element_type>::value == false && std::is_nothrow_move_constructible<element_type>::value == true). Такой подход позволил бы расширить применимость вектора.
Кто даст гарантии, что это не поломает существующего кода? Вызов деструктора уж всяко приведёт к регрессу производительности. На это пойти нельзя.
Может быть и можно, расписав хитрые специализации, добиться, чтобы для старого кода работало по-старому, а для нового, где нужно, по-новому. Но это отодвинет стандартизацию ещё лет на пять и сделает и так непростую логику ещё сложнее и запутаннее. Проще огородить заборчиками контрактов и расставить знаки UB.
gegMOPO4:
M>>В большинстве случаев семантика копирования для копирующих конструкторов и для копирующих операторов присваивания едина, и, в частности, некоторые функции std::vector-а полагаются на это свойство. Двойственная семантика копирования — потенциальный источник ошибок. В CWG (Core Working Group) это, похоже, хорошо понимают.
MOP>Следовательно, на параметры std::vector должно накладываться ещё одно ограничение. А вернее, только на некоторые операции.
Так-то оно так, да только статически проверить неидентичность семантики копирования у конструктора и оператора присваивания в общем случае не представляется возможным. А вот статически проверить отсутствие оператора присваивания очень даже просто.
MOP>Может быть стоило и присваивание кортежей со ссылками запретить. Но, возможно, у необходимости присваивания были веские причины. Подозреваю, что присваивание кортежу со ссылками станет идиомой и одним из основных применений кортежей со ссылками.
И что, обязательно использовать именно = ?
M>>В LWG к диагностике потенциальных и даже явных ошибок относятся весьма легкомысленно (как, в общем-то, и к документированию стандартной библиотеки), так что убеждать её представителей в целесообразности замены operator= на другую функцию, похоже, бесполезно.
MOP>Это нарушит совместимость.
Совместимость с C++03 это не нарушит. В C++03 tuple-ов вообще нет, а pair-ы со ссылками запрещены.
MOP>А как вы обеспечите std::copy в контейнер с кортежами ссылок? Изменение объектов, на которые ссылаются, а не переназначение ссылок является требуемым поведением.
Если такие ситуации редки, можно использовать циклы. Если такое нужно часто, можно ввести прокси-итераторы, для которых *iter = x эквивалентно iter.referenced_object().assignment_function(x).
M>>Во-вторых, перемещение элементов в std::vector можно выполнять с помощью деструктора и не бросающего исключений move-конструктора (когда std::is_move_assignable<element_type>::value == false && std::is_nothrow_move_constructible<element_type>::value == true). Такой подход позволил бы расширить применимость вектора.
MOP>Кто даст гарантии, что это не поломает существующего кода?
При std::is_move_assignable<element_type>::value == false insert у vector-а на данный момент вообще не обязан работать (предусловие не соблюдается). Это достаточно весомый аргумент?
MOP>Вызов деструктора уж всяко приведёт к регрессу производительности.
С чего бы это вдруг?
MOP>Может быть и можно, расписав хитрые специализации, добиться, чтобы для старого кода работало по-старому, а для нового, где нужно, по-новому. Но это отодвинет стандартизацию ещё лет на пять и сделает и так непростую логику ещё сложнее и запутаннее.
Да там совсем незначительные дополнения надо внести. Я хоть и очень невысокого мнения об LWG, но всё же сомневаюсь, что решение таких вопросов у них могло бы занять столько времени.
MOP>Проще огородить заборчиками контрактов и расставить знаки UB.
Здравствуйте, jyuyjiyuijyu, Вы писали:
СВВ>>>Аннотация: СВВ>>>Несколько веселых и интересных примеров на языке C++.
PD>>Единственное, что в этих примерах занимательного — это детское удивление автора, впервые открывшего для себя язык С++, и спешащего поведать о своем удивлении всему миру.
J>К молодым людям нельзя относиться свысока.
Ну автора можно понять. По-настоящему удивительно, почему такую статью приняли к публикации .
Здравствуйте, Masterkent, Вы писали:
M>Раз уж завели такой топик, покажу один из подарочков, уготованных нам комитетом по стандартизации C++: M>http://ideone.com/CoyN7 M>Howard Hinnant из Library Working Group, с которым я недавно беседовал, не видит ничего плохого в том, что у std::pair и std::tuple при инстанцировании их ссылочными типами copy/move конструкторы делают совсем не то же самое, что copy/move операторы присваивания, и вот такое необычное поведение программы его, похоже, полностью устраивает.
Кстати, оно фиксится вот так:
#include <vector>
#include <new>
struct test
{
int _i;
int& _r;
test() : _i(-1),_r(_i) {} //Just to resolve compilation error in case of reference in class member.
test(int& r) : _i(0),_r(r) {}
test(const test& t) : _i(0),_r(t._r) {}
void operator=(const test& o)
{
this->~test();
new (this) test(o);
}
};
int static_arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int main(void)
{
std::vector<test> arr;
for(int i = 0; i < 10; i++)
{
arr.push_back(test(static_arr[i]));
}
for(int i = 0; i < 10; i++)
{
printf("%i - %i\n",i,arr[i]._r);
}
printf("\n");
arr.insert(arr.begin()+5,static_arr[9]);
for(int i = 0; i < 11; i++)
{
printf("%i - %i\n",i,arr[i]._r);
}
return 0;
}
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
#include <iostream>
#include <new>
struct test
{
int _i;
int& _r;
test() : _i(-1),_r(_i) {} //Just to resolve compilation error in case of reference in class member.
test(int& r) : _i(0),_r(r) {}
test(const test& t) : _i(0),_r(t._r) {}
void operator=(const test& o)
{
this->~test();
new (this) test(o);
}
};
int main()
{
test t;
int n = 1;
std::cout << t._r << std::endl; // вывод: -1
t = test(n);
std::cout << t._r << std::endl; // undefined behavior (3.8/7), имеет право снова вывести -1
}
Здравствуйте, Masterkent, Вы писали:
M> t = test(n); M> std::cout << t._r << std::endl; // undefined behavior (3.8/7), имеет право снова вывести -1
Не может, там конструктор копии вызывается.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Vain:
M>> t = test(n); M>> std::cout << t._r << std::endl; // undefined behavior (3.8/7), имеет право снова вывести -1 V>Не может, там конструктор копии вызывается.
Нолик более вероятен, но сути это не меняет — в данном случае поведение не определено. Компилятор вправе считать, что оба выражения t._r ссылаются на один и тот же адрес, несмотря на то, что объект t уничтожили и на его месте создали новый.
Здравствуйте, Masterkent, Вы писали:
M>>> t = test(n); M>>> std::cout << t._r << std::endl; // undefined behavior (3.8/7), имеет право снова вывести -1 V>>Не может, там конструктор копии вызывается. M>Нолик более вероятен, но сути это не меняет — в данном случае поведение не определено. Компилятор вправе считать, что оба выражения t._r ссылаются на один и тот же адрес, несмотря на то, что объект t уничтожили и на его месте создали новый.
Почему?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Vain:
M>>>> t = test(n); M>>>> std::cout << t._r << std::endl; // undefined behavior (3.8/7), имеет право снова вывести -1 V>>>Не может, там конструктор копии вызывается. M>>Нолик более вероятен, но сути это не меняет — в данном случае поведение не определено. Компилятор вправе считать, что оба выражения t._r ссылаются на один и тот же адрес, несмотря на то, что объект t уничтожили и на его месте создали новый. V>Почему?
Видимо, в комитете по стандартизации посчитали, что оптимизация обращения к ссылкам и константным полям класса более полезна, чем такие игры со временем жизни объектов.
Здравствуйте, Masterkent, Вы писали:
M>>>>> t = test(n); M>>>>> std::cout << t._r << std::endl; // undefined behavior (3.8/7), имеет право снова вывести -1 V>>>>Не может, там конструктор копии вызывается. M>>>Нолик более вероятен, но сути это не меняет — в данном случае поведение не определено. Компилятор вправе считать, что оба выражения t._r ссылаются на один и тот же адрес, несмотря на то, что объект t уничтожили и на его месте создали новый. V>>Почему? M>Видимо, в комитете по стандартизации посчитали, что оптимизация обращения к ссылкам и константным полям класса более полезна, чем такие игры со временем жизни объектов.
Компилятор вправе считать, что оба выражения t._r ссылаются на один и тот же адрес, несмотря на то, что объект t уничтожили и на его месте создали новый.
а реальный пример можно?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]