Здравствуйте, MTD, Вы писали:
MTD>Я преподаю С++ студентам пару лет и неизменно вижу круглые непонимающие глаза когда я рассказываю о копировании, о том, что тут передача по значению, тут по ссылке, а тут вообще по константной ссылке. Когда же после этого речь заходит про перемещение, то у части эмоционально нестойких начинается нервный тик. Итак мои тезисы: копирование и перемещение неоправданно сложны, не интуитивны, приводят к снижению быстродействия и ошибкам. Передача объектов по ссылке на объект всегда достаточна.
Может ты пытаешься втолкнуть всё сразу, отчего и происходит непонимание у студентов?
Копирование — это проще простого, понятно на интуитивном уровне, и не должно вызывать никаких проблем. В C++ все value типы не только поддерживают копирование, но и делают это одинаковым образом. Т.е. std::vector можно скопировать точно так же как и int без каких-либо специфичных для вектора особенностей. На начальном уровне достаточно просто ограничиться копированием без ссылок и перемещений: объявлять все переменные по значению, передавать все параметры по значению, всегда возвращать по значению, и т.д.
Сложные типы тоже работают с копированием без проблем или лишних телодвижений:
То, что копирование неоптимально, на начальном этапе совершенно неважно. Гораздо более важно понимание основных принципов программирования.
Я считаю, ссылки стоит обсуждать после ознакомления с указателями. Если понимаешь, что такое указатель и как с ним работать, понять ссылки — тривильная задача. И наоборот, если указатели вызывают проблемы, освоить ссылки будет ещё более трудной задачей.
А перемещений вообще в языке не было до C++11. Это чистого рода оптимизация, которую можно оставить для продвинутых студентов.
Здравствуйте, MTD, Вы писали:
_>>Ну идея описана верно, но реализация весьма далека от реальности MTD>Пример должен быть простым и иллюстрировать идею, пытаться в двух предложениях описать все мироустройство обречено на провал. Вот ты, например, не поленился и накопипастил кода — усердие достойное похвалы, но для новичка код сложный, а для тем кто в теме и так все понятно.
Всё верно, но при условии что упрощение не нарушает базовых принципов. А твой пример их нарушал — буфер SSO существует сам по себе (и своим существованием увеличивает размер экземпляра класса string), а не только как union с указателем.
_>>неужели преподаватель C++ никогда не заглядывал в исходники стандартной библиотеки? ) MTD>Ох, ребята, я с вас умиляюсь, вы все про меня лучше меня знаете: и куда я заглядывал, и что я знаю, и сколько у меня опыта, и нужно ли мне преподавать. Впрочем если вам это помогает справиться с какими-то личностными проблемами — вперед, мне не жалко.
Ну это ты на самом деле сам подставился как раз информацией о том, что являешься преподавателем C++ у студентов. Если бы просто какой-то программист на форуме начал подобное обсуждение и при этом допустил бы пару мелких косяков, то скорее всего никто и не обратил бы внимания (ну мало ли, неопытный коллега, в этом нет ничего страшного), сосредоточившись на теме обсуждения. А вот преподавателю всегда все косяки отметят, потому как он априори не должен их допускать. )))
Здравствуйте, alex_public, Вы писали:
_>А твой пример их нарушал — буфер SSO существует сам по себе (и своим существованием увеличивает размер экземпляра класса string), а не только как union с указателем.
Что-то я не помню в стандарте таких требований к реализации.
_>А вот преподавателю всегда все косяки отметят, потому как он априори не должен их допускать. )))
Да, так должно быть и я к этому стремлюсь. Спасибо, коллеги, помогаете разобраться в некоторых вещах!
_>>>В частность операция перемещения это просто желание убрать лишние операции при передаче результата. MTD>>Это понятно и на это студенты резонно замечают, что не было бы копирования не надо было бы и костылей в виде перемещения. LVV>Сравните С++ и C# (или Java) в своих лекциях. LVV>Копирование — это не костыли, а один из вариантов семантики. LVV>В C# тоже ж добавили костылей — value class (классы-значения). LVV>B метод clone() — это такой же костыль для реализации копирования в языке, где перемещение — основная семакнтика.
C++ провозглашает принцип "не платишь за то, что явно не используешь" и неявное копирование этому противоречит. А вот метод clone() — полностью подходит под данный принцип.
Здравствуйте, MTD, Вы писали:
V>>С++ вообще неоправданно сложен не интуитивен. Вот бургеры переворачивать в МакДональдспе — просто и интуитивно. MTD>Печально, что вместо конструктивного обсуждения, интеллекта хватило только для перехода на личности.
Ну ты ж переходишь на личности (1
Meyers:
M>C++ провозглашает принцип "не платишь за то, что явно не используешь" и неявное копирование этому противоречит.
Про упоминание явности использования я что-то не припомню. "Неявность" операции, под которой подразумевается отсутствие применения отдельно выделенной именно под данную операцию конструкции, — это вообще как бы следствие использования абстракции, когда отдельно взятая операция становится лишь частью чего-то более масштабного. Абстракции же для того и существуют, чтобы всякий раз не возиться с каждой мелочью по отдельности. И я вот откровенно не понимаю, почему некоторые считают, что определяемые пользователем абстракции, такие как функции (позволяющие объединить несколько действий в одно в соответствии с правилами, определяемыми программистом), — это нормально, а вот абстракции, определяемые самим языком (позволящие объединить несколько действий в одно в соответствии с некими стандартизированными правилами) — это типа плохо.
M>А вот метод clone() — полностью подходит под данный принцип.
Хорошо, предположим, что от более высокоуровневой абстракции "инициализация объекта" (потенциально имеющей своей составной частью вызов конструктора копирования) мы перешли к менее высокоуровневой абстракции "прямой вызов функции копирования для инициализации объекта". И что мы с этого поимеем? От этого наша программа сразу станет работать быстрее? Эта clone, поди, тоже "неявно" кучу операций может делать — пока под её капот не заглянешь, не узнаешь, чем она там занимается. А вдруг она неэффективная и в данном конкретном месте копирование можно было бы реализовать лучше? А ну-ка, давайте и её расщепим на атомы, и вставим все её потроха по месту копирования, шоб все действия перед глазами были.
M>C++ провозглашает принцип "не платишь за то, что явно не используешь" и неявное копирование этому противоречит. А вот метод clone() — полностью подходит под данный принцип.
1. Тогда претензии — к Си. Семантика копирования взята оттуда.
2. Неявное копирование ссылок в С# или Java — не лучше.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, MTD, Вы писали:
MTD> копирование и перемещение неоправданно сложны
хм. а в чем именно сложность? operator= и .ctor генерируются по умолчанию, если ты не работаешь напрямую с памятью
MyType(MyType&&) = default; тоже никакой сложности не добавляют
MTD> не интуитивны, приводят к снижению быстродействия и ошибкам
для студентов — возможно, но это верно для любой технологии. Ты же не даешь детям циркулярную пилу.
MTD> Передача объектов по ссылке на объект всегда достаточна
по указателю,
Да, достаточна, но это приводит к пакостям, которые демонстрирует .net/java
побайтовое копирование для нее не работает, поэтому чтобы успешно запихивать ее в контейнер есть 2 варианта
а) move || copy, чтобы можно было на стеке создать
б) всегда создавать ее только в хипе.
Для плюсов это дурацкая затея из-за ликов и жуть как медленно
конечно Java/.net идут 3м вариантом, запрещают голые указатели, но в плюсах такое нельзя
Пример 2.
Параллелизм
Дотнетчики обожают код типа такого
class SomeContext
{
List<...> _lazyProp;
... тут еще квадрилион полей
bool IsOK(Object^ o)
{
if (_lazyProp == null)
_lazyProp = InitProp();
......
}
}}
и грандиозно обламываемся c thread_local myCtx , потому что копирование у них не принято, а SomeContext — тянет за собой иерархию из 50 классов, из которых копироваться умеют 2
Пример 3.
Классы задач с undo/redo
— транзакцонность
— откат состояния объекта после исключения
— lock-free и MVCC-контейнеры
— undo|redo в редакторах
все это реализуется в разы проще, если есть копирование
MTD> Обоснуйте почему копирование и перемещение это не WTF, а классно, чтобы я смог студенту обосновать, что все сделано правильно.
все сделано правильно, но студентам еще рано об этом знать, это инструмент для больших дядей экскаваторщиков, а они пока ямки под саженцы учатся копать
Здравствуйте, MTD, Вы писали:
MTD>Передача объектов по ссылке на объект всегда достаточна.
Нет, по двум причинам, которые являются киллер-фичами языка:
1) Ломается ABI, поскольку ещё в Си можно передавать даже структуры — как по значению, так и по указателю.
2) Ломается возможность оптимизации.
Вот смотри: если ты передаёшь в функцию нечто по ссылке или указателю, то
— вынужден приземлить этот объект в адресуемую память
— адрес сам занимает память, — для мелких объектов это соизмеримо с их собственными размерами
— вызываемая сторона не может быть уверена в уникальности адреса аргумента и в неизменности состояния объекта
const X* z;
void f(const X& x, const X& y) {
if (&x == &y) { zombie_code(); }
if (&x == z) { zombie_code(); }
X t = x;
foo();
if (t != x) { zombie_code(); } // нет гарантии, что никто не поменял состояние объекта x извне
}
void g(X x, X y) {
if (&x == &y) { can_be_elided(); }
if (&x == z) { can_be_elided(); }
X t = x;
foo();
if (t != x) { can_be_elided(); } // невозможно поменять локальную переменную, если её адрес не отдали наружу
}
MTD>1. Получить копию. Но явное лучше неявного, поэтому метод clone гораздо лучше. Да, уже не напишешь bigint или комплексное число с которым удобно работать как с примитивными типами, но почему эти типы просто не сделать частью языка?
Таких типов слишком дофига, чтобы делать их частью языка.
Если тебе нужен тип с семантикой значения, берёшь и почти ничего не делаешь.
Если нужен тип БЕЗ семантики значения, берёшь и отламываешь.
MTD>2. Умные указатели. Должны быть частью языка и все будет ок.
Они и есть часть языка — std::unique_ptr и std::shared_ptr.
Только смысл у всех трёх указателей — голых и двух умных — несколько разный.
Можно, конечно, придумать синтаксический сахар (как управляемые указатели в C++/CLI — с крышечкой). Но это избыточно.
MTD>Обоснуйте почему копирование и перемещение это не WTF, а классно, чтобы я смог студенту обосновать, что все сделано правильно.
Чтобы обосновать, надо начать с другого конца. А именно, с основ ООП, где проводится черта между уникальными и неуникальными данными.
С++ поддерживает обе семантики — и объекта, и значения.
В отличие от C#, где объекты отдельно, значения (структуры и примитивные типы) отдельно.
Ну а дальше начинаются вопросы: как эффективно реализовать семантику значения в разных условиях.
— когда данные легковесные или тяжеловесные
— когда явно нужно клонировать, а когда можно и сократить
— когда компилятор сам догадается, что можно сократить (все случаи copy elision, RVO, NRVO), а когда ему нужно подсказывать (std::move, например), а когда и реально приложить усилия (расшарить внутренние данные, реализовать copy on write)
Здравствуйте, Meyers, Вы писали:
К>> в Си можно передавать даже структуры — как по значению, так и по указателю.
M>Никакой "передачи по указателю" не существует. Есть взятие адреса и передача этого адреса по значению. Так что в Си есть только передача по значению.
Ну, во-первых, есть случай возврата большой структуры — который в подавляющем большинстве ABI реализуется как передача указателя вызывающей функцией скрытым параметром, а для программиста это возврат по значению.
Во-вторых, даже если б формально Вы были правы, по сути это бессмысленно — указатель в >99.99% не самоцель, а метод передачи того, на что он указывает.
M>Никакой "передачи по указателю" не существует. Есть взятие адреса и передача этого адреса по значению. Так что в Си есть только передача по значению.
Здравствуйте, MTD, Вы писали:
MTD>Здравствуйте, torvic, Вы писали:
MTD>>>Я преподаю С++ студентам пару лет.... T>>первым языком?
MTD>Нет, они уже как правило много чего еще знают, поэтому и недоумевают зачем в С++ так сделано.
Точнее: они много чего еще НЕ знают, поэтому и недоумевают зачем в С++ так сделано.
Основная идея конструктора копии — сделать полноценную копию объекта,
которая не зависит от исходного объекта (её можно изменять, не затрагивая исходный объект).
Для этого выделяется память и содержимое исходного объекта копируюется на новое место.