LINUX.ORG.RU

Карта указателей, ссылок и for по диапазону

 , ,


0

3

Привет, ЛОР.

Тут такая задачка возникла. Поступает на вход массив неких структур, которые в чём-то схожи, а в чём-то различны и обрабатывать их надо и как единое целое, и по типам (записи разных типов перемешаны). Чтобы не копировать данные туда-сюда и вообще не нарушать целостность, я подумал «а давай-ка сделаю три контейнера ссылок и пусть они ссылаются на оригинальные структуры». Важно: структуры в процессе обработки меняются, т.е. константными я их делать не могу.

Попробовал примерно так:

struct AnyS {
    int a, b, c;
};

std::list<AnyS> anyList;
std::map<int, AnyS&> refs0, refs1;

    AnyS any;
    any.a = 0;
    anyList.push_back(any);

    int i=0;
    for (AnyS& yaAny: anyList) {
        if (yaAny.a==0)
            refs0[i] = yaAny;
        // Тут м.б. рациональнее было бы сделать switch, но не суть, это демо
        i++;
    }

На строке с присвоением получаю ошибку

/usr/include/c++/11/tuple:1824: ошибка: value-initialization of reference type ‘AnyS&’

Т.е. оно пытается присваивать по значению, хотя я хотел явно противоположного, и у него это не получается.

А что самое смешное? А то, что если я отказываюсь от ссылок и работаю по старинке с указателями, то всё прекрасно работает! Примерно так:

// ...
std::map<int, AnyS*> refs0, refs1;
//...
    for (AnyS& yaAny: anyList) {
        if (yaAny.a==0)
            refs0[i] = &yaAny;
        i++;
    }

Но нельзя ли всё-таки как-то заставить работать вариант со ссылками? Они, типа, безопаснее, и красивее и вообще сейчас считается, что работа с указателями это фи.

Я, конечно, догадываюсь, что я как-то неправильно использую range-based for… но вот как правильно…

P.S. GCC 11.3.1, если это важно.

★★★★★

Последнее исправление: hobbit (всего исправлений: 2)

Но нельзя ли всё-таки как-то заставить работать вариант со ссылками? Они, типа, безопаснее, и красивее и вообще сейчас считается, что работа с указателями это фи.

Я сейчас тебя шокирую))) Ссылки - это синтаксический сахар в плюсах над указателями, которые необходимо проиницилизировать и нельзя переприсвоить, есть еще тонкости с константным рефенсом. Первое это как раз почему у тебя не компилируется. refs0[i] пытается создает непроинициализированный референс. Выход или сразу создавать проинициилизированный через emplace или использовать указатели. Можно через reference_wrapper. У последнего под капотом указатель.

И скорее всего тебе стоит смотреть в сторону multi_index и не изобретать велосипед.

PRN
()

Т.е. оно пытается присваивать по значению,

Нет, вы не понимаете указатели С++. Ссылка должны получать значение при инициализации, она не может быть не инициализированной. Поэтому, как уже предложили выше, нужно выбрать способ создания элемента с ссылкой сразу по месту, благо у std::map он есть - emplace().

raspopov
()
Ответ на: комментарий от PRN

Ссылки - это синтаксический сахар в плюсах над указателями

Я в курсе, спасибо.

Boost ради одного multi_index тащить пока неохота, а вот emplace(), пожалуй, попробую.

hobbit ★★★★★
() автор топика

Присвоение со ссылкой и не будет работать, потому что ссылку инициализируют во время создания, по другому синтаксис не позволит. М.б. emplace и поможет, но в любом случае оно выглядит криво по сравнению с std::map<int, AnyS*>.

А вообще ЯННП зачем такие сложности. Возникает смутное ощущение что можно как то сильно проще, но нужна вменяемая формулировка задачи;-)

ЗЫ если три контейнера с пойнтерами разобранными по типам, то вот так гораздо более прямо:

std::vector<AnyS> input;

std::vector<AnyS*> type0, type1, type2; 
// или даже так
std::vector<int> type0, type1, type2; 

std::vector::push_back работает несоизмеримо быстрее чем добавление в std::map, map оправдан только если индексы разрежены и нужен поиск по индексу. Да и обход вектора работает быстрее.

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 5)
Ответ на: комментарий от AntonI

std::vector

Ты предлагаешь в качестве этого int использовать индексы на элементы в исходном списке/массиве?

Тоже над этим думал. Правда, сегодня я что-то успокоился и думаю оставить вариант с указателями, работает и неплохо.

…А вот если бы это была Java…

hobbit ★★★★★
() автор топика
Ответ на: комментарий от hobbit

Ты предлагаешь в качестве этого int использовать индексы на элементы в исходном списке/массиве?

Да

А вот если бы это была Java

То эта бабушка была бы дедушкой:-)

AntonI ★★★★★
()

Они, типа, безопаснее, и красивее и вообще сейчас считается, что работа с указателями это фи.

Не надо слушать всякое дерьмо и страдать на ровном месте из-за какого-то криворукого идиота. Во-первых, указатель != массив (не надо о нем так думать), для этих целей последовательность можно передавать как какой-нибудь std::span. Во-вторых, ссылка как поле класса - это вообще последнее дело, никогда так делать не надо, нарушается станадртная логика копирования/перемещения. Если что я не против ссылок, но если удобно сделать указатель, то нет никаких причин этого не делать, ибо назовут немодным

kvpfs_2
()
Ответ на: комментарий от kvpfs_2

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

Я, в общем-то, целиком согласен. И скорее всего, я так и оставлю, благо работает.

Просто тут есть ещё один аспект… спортивно-теоретический. :))) А что если придётся такое же делать на языке, в котором указателей вообще нет, а ссылки есть? Я понимаю, это уже как выше написали, «бабушка была бы дедушкой», но от сумы и тюрьмы, как говорится… :)))

Ну на крайняк да, идею с индексами никто не отменял, пожалуй, самый универсальный вариант.

hobbit ★★★★★
() автор топика
Ответ на: комментарий от hobbit

Ну на крайняк да, идею с индексами никто не отменял, пожалуй, самый универсальный вариант.

Ну так, для разнообразия - ещё можно сохранять итераторы контейнера. Костылил как-то, в листе сохранялись объекты, а мепах хранились разные способы среференсить эти объекты - по имени, ID, … . Плюс итераторов - легкое удаление из контейнера (если контейнер не инвалидит их при росте, конечно же)

kvpfs_2
()
Ответ на: комментарий от PRN

я не совсем понимаю, как тут reference_wrapper применить. ( fluorite)

std::map<int,int&> m1;
m1[1];

/usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include/g++-v10/tuple:1693:70: error: value-initialization of reference type ‘int&’

std::map<int,std::reference_wrapper<int>> m2;
m2[1];

/usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include/g++-v10/tuple:1693:70: error: no matching function for call to ‘std::reference_wrapper<int>::reference_wrapper()’

MirandaUser2
()

А то, что если я отказываюсь от ссылок и работаю по старинке с указателями, то всё прекрасно работает!

работает? не трожь!

карочи. как раз указатели и надо тут использовать. ссылки не для этого дела предназначены.

компилятор как бы пытается тебе это сказать, а ты с ним бодаться решил.

вообще сейчас считается, что работа с указателями это фи.

ты мудаков-то не слушай, а то в беду попадешь.

alysnix ★★★
()
Ответ на: комментарий от MirandaUser2
#include <map>
#include <functional>
#include <iostream>

struct AnyS {
    int a, b, c;
};

int main() {
    AnyS as0 { .a = 123, .b = 234, .c = 345 };
    AnyS as1 { .a = 456, .b = 567, .c = 678 };
    AnyS as2 { .a = 789, .b = 890, .c = 901 };

    std::map<int, std::reference_wrapper<AnyS>> m {
        {0, std::ref(as0)},
        {1, std::ref(as1)},
        {2, std::ref(as2)}
    };

    auto wrapped_ref = m.at(1);
    std::cout << wrapped_ref.get().b << std::endl;

    as1.b = 42;
    std::cout << wrapped_ref.get().b << std::endl;

    return 0;
}
fluorite ★★★★★
()
Ответ на: комментарий от fluorite

а через индексатор то как-работать?
Потому что при использовании emplace работает и std::map<int,AnyS&> (как у ТС изначально)
А если мне нужен не emplace, а assign, то и твой вариант с std::map<int,std::reference_wrapper<AnyS>> не работает.

m[1]=std::ref(as0);

/usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include/g++-v10/tuple:1693:70: error: no matching function for call to ‘std::reference_wrapper<AnyS>::reference_wrapper()’

P.S. insert_or_assign - добавлен только в C++17
P.P.S. можно конечно и через find() и at() производить assign, но опять же непонятно в чём тут польза от std::reference_wrapper

MirandaUser2
()
Последнее исправление: MirandaUser2 (всего исправлений: 2)
Ответ на: комментарий от hobbit

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

Это как О_О? Ссылка (в плюсовых терминах) это указатель покрытый приторной синтаксической глазурью. Если нет указателей то нет и ссылок, нечего покрывать… Бывает наоборот, когда ссылок в ЯП нет а указатели есть.

идею с индексами никто не отменял, пожалуй, самый универсальный вариант.

Этот вариант еще и сериализуется из коробки, в отличие от всяких указателей. Иногда это важно.

Но вообще здесь определяющим является контейнер в котором лежат исходные объекты. Если вектор - то целочисленные индексы, если список - то итераторы или указатели (в зависимости от того надо объекты удалять или нет).

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от AntonI

например «алгол»-паскаль чё нить подобное аля

есть var аргументы

но нет ^ для взятия адресса

и рантайм бдит за нарушение границ

случай где ссылки есть а указателей нет

поэтому если хотить обмазываться в указатели как таковые пулим шмат памяти под массив(под свой алокатор деалокатор менеджья меморья )- нашеСуказателями где индексы этого массива вполне указатели

qulinxao3 ★☆
()
Последнее исправление: qulinxao3 (всего исправлений: 1)
Ответ на: комментарий от hobbit

…А вот если бы это была Java…

Там оригинальный код без ошибки бы работал, так как нету запутанных правил.

Если нету ссылок и указателей, то через индексы их можно делать.

MOPKOBKA ★★★★★
()
Ответ на: комментарий от qulinxao3

Можно еще питон вспомнить, ага;-)

Спор чиста терминологический. Конечно можно постановить, что указатель это как ссылка (но с возможностью адресной арифметики), а ссылка это как указатель (но без возможности адресной арифметики).

ИМНО если че то из двух ипостасей нету, то оставшуюся можно называть хоть указателем хоть ссылкой. В питоне нет проблем напихать в массив ссылок, они же указатели.

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от AntonI

Спор чиста терминологический.

Ссылка - это «синоним» объекта: к одному и тому же объекту обращаются по разным именам.

Указатель - это объект/ячейка, содержащая ссылку (см. выше).

anonymous
()
Ответ на: комментарий от AntonI

имха указатель это индекс (число) которое можно арифметикой отмодифить ( переприсвоение возможно но не обязательно) для навигации по соседям исходного адресата

ссылка это буквально хэндл - ручка ; крючок подмонтировавшись можем копошиться и шибуршать через этот лаз в открывшемся подпространстве имён

забавно как в изначальном алголе было открыто вычисляемые по месту передача аргументов по имени - и как закрыто во всей индустрии ибо уж слижком большой приреквест к пользователям такой механики ( по факту это лямбды :) c динамическим скоупом )

https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name

https://www.google.com/search?q=Программист-навигатор Чарльз В. Бахман

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

qulinxao3 ★☆
()
Последнее исправление: qulinxao3 (всего исправлений: 4)
Ответ на: комментарий от MirandaUser2

я не совсем понимаю, как тут reference_wrapper применить

https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper/reference_wrapper

У reference_wrapper нет дефолтного конструктора, поэтому его нельзя использовать в виде m2[1] = ..., нужно использовать emplace. Я имел в виду, использовать ТСу reference_wrapper, если у него есть какие-то предрассудки на счет рав поинтера. Хотя в не владеющих указателях нет ничего плохого и это тоже самое (почти) что и ссылки. reference_wrapper здесь может помочь, по сравнения с обычными ссылками, только если ссылку надо переприсвоить.

PRN
()
Последнее исправление: PRN (всего исправлений: 1)
Ответ на: комментарий от kvpfs_2

Ссылка полем класса последнее дело? Сомнительно. Как минимум та же гарантия на необходимость проинициализировать, а по поду, отсутствие сомнений на необходимость проверки на nullptr. Но это при условии, что применение ссылки архитектурно безопасно, для примера: владелец объекта, на который ссылка, владеет и объектами, в которые объект передаётся и время жизни его дольше его пользоаателей.

hatred ★★★
()
Ответ на: комментарий от hatred

Как минимум та же гарантия на необходимость проинициализировать

Это всё заботы конструктора и снаружи никого вообще не волнует. А вот то что отвалится копирующий/перемещающий oerator= с высокой вероятностью окажется нежелательным эффектом.

а по поду, отсутствие сомнений на необходимость проверки на nullptr

Ну ё-моё, ссылка - это всего лишь указатель на минималках. Из указателя можно слепить что угодно. Например, сделать typedef для указателей

not_null<int *> m_ptr;

всем становится понятно, что null’а там быть не может и никаких проверок делать не надо, а если какой шальной нуль и залетит, то санитары отловят. Если очень хочется, то можно обертку пожирнее, с проверкой в дебаге. Но, по-моему, достаточно простого typedef’a.

kvpfs_2
()
Ответ на: комментарий от AntonI

map оправдан только если индексы разрежены и нужен поиск по индексу.

Скорее если часто вставляют и/или удаляют из середины, и/или когда нужна стабильность адресов нод. А так то имеет смысл посмотреть на boost::flat_map<>.

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Ответ на: комментарий от hobbit

Boost ради одного multi_index тащить пока неохота

У multi_index есть существенное преимущество: если нужно бежать по списку и удалять то лишних lookups в map не будет.

bugfixer ★★★★★
()
Ответ на: комментарий от AntonI

«она там бесплатна» - (как в современных dict сохраняется порядок вставки (в cpython3)

выше шутка в том наборе контейнеров совмещенны листья в виде массивов и остроумно(но без «умничанья» всяких деревьев Ван-де-Бооса и прочей амортизаторщины) сд+алго вставки поиска

ибо список( ну тот который база: аля T:(Tpayload payload,T *next) ) плох во всём кроме динамического стека произвольной длины

qulinxao3 ★☆
()
Ответ на: комментарий от kvpfs_2

Ну ё-моё, ссылка - это всего лишь указатель на минималках.

да не нервничай :) знаю. Ровно как почему иногда вернуть структуру через указатель/ссылку выходит дороже, чем по значению.

Есть классы которые привязаны к ресурсам и никак не могут быть мувнуты/скопированы через operator=. Да просто добавь std::mutex в класс, у тебя уже не пройдёт:

Foo foo1;
Foo foo2;

Foo foo3 = foo1; // ошибка
foo2 = foo1; // ошибка

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

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

в рантайме == оверхед.

Так что я против догматичного «ссылка - последнее дело». Нужно ссылку и она проходит по архитектуре - получаем гарантии и меньше оверхеда на проверках. Не подходит - указатель и обкладываемся врапперами и/или проверками.

hatred ★★★
()
Ответ на: комментарий от AntonI

Тут имелось в виду («ванга мод»), что в std::map ключи упорядочены, условно, используя std::less. А значит отсортированы. Т.е. платишь как бы за вставку, а бонусом - сортировка :)

hatred ★★★
()
Ответ на: комментарий от hatred

Ну нужно копировать, ну и делай указатель со всеми вытекающими.

Никто ничего не знает на этапе написания «объектного скелета» как в точности будет юзаться вот эта вот сущность. Предлагаете какую-то унылую дрочь с тонкой настройкой и переписыванием объектов при необходимости. Не нужно париться, просто не ломай дефолтную логику и проблем на ровном месте себе не делай.

в рантайме == оверхед.

Это всё, что вы там заметили? Во-первых, рантайм в дебаге, а во-вторых, почти полный аналог ссылки - это врапер с удаленным nullptr_t конструктором, всё в компалтайме. Но как говорил выше - мне хватает простого typedef’a (я пользуюсь этим в реальности). Вообще not_null рекомендовался в «C++ Core Guidelines» если не ошибаюсь. В моем случае что-то вроде

template <typename T>
requires ... // check that T is pointer
using not_null = T;

оверхед

Мне всегда забавно становится, когда условный косвенный вызов инициирует разговоры с такими нотками. Что вы там пишите такое? Голые циклы, которые не делают ничего кроме инкремента своего счетчика? Ерунда это всё в хоть сколько-то реальных программах с какой-то полезной нагрузкой (без крайностей, конечно. Но какие-то обычные практики по типу виртуальность, нет инлайна из-за отдельного cpp, …).

Писал как-то код на STM’ку, юзал HAL. В итоге скорость выполнения был не против увеличить. Что же делать? Очевидно, что тормозной HAL крадет все производительность со своими проверками, так говорили многочисленные господа в интернете. Переписал, напрямую юзал регистры, все максимально прямо. Что я выйграл? Да ничего значимого почти

kvpfs_2
()
Ответ на: комментарий от kvpfs_2

почти полный аналог ссылки - это врапер с удаленным nullptr_t конструктором, всё в компалтайме.

Даже уже готовое есть

int i;
std::reference_wrapper<int> w(i);

Инициализация как вы любите, обязательная, а объект copyable/moveable, ибо внутри указатель. Но никогда нельзя делать полями класса ссылки, это очень ужасно-костыльно

kvpfs_2
()
Ответ на: комментарий от hatred

ванга мод на себя прошлого:

подразумевалось наличие большего букета структур данных чем массив_воистину(чанк памяти(молекула) из адресуемых линейно атомов) и спск(одно связный с 64бит указателем и байт пэйлоада всё размазано мимо кэш спекуляции)(который замена пушдаун магазину)

qulinxao3 ★☆
()
Ответ на: комментарий от AntonI

Да да да, это понятно! Я шутканул по теме: ну ты же вставку сделал, а не сортировку, а данные всё равно отсортированы, значит сортировка вышла бесплатно :) То что суть вставки, по сути, в бинарном поиске и ребалансировке… «это другое» :)

hatred ★★★
()
Ответ на: комментарий от kvpfs_2

Что вы там пишите такое?

Числодробилка. Не суть.

В моем случае что-то вроде

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

foo(nullptr);

?

Тот же gsl::not_null проверяет в конструкторе на nullptr переданный параметр: указатель может прийти откуда-то из недр других вызовов и там окажется нуль. Проверить это в компайл тайме у тебя не выйдет простым using. Да, используемый likely в gsl::not_null немного даст оптимизации, но полностью проверки не выкинет.

Что бы синхронизироваться, я правильно понимаю, что not_null предлагается использовать как поле класса? Если исходить из реализации gsl::not_null, то там нет дефолтного конструктора (в противном случае nullptr там может оказаться), тогда оно аналогично ссылке должно проинициализироваться при создании, но бонусом будет возможность переинициализироваться и скопироваться/мувнуться. Дальше по коду можно использовать _ptr->foo() без доп проверок, так была введена гарантия на то, что при уже при создании или при присваивании там не null. Верно?

hatred ★★★
()
Ответ на: комментарий от kvpfs_2

Но никогда нельзя делать полями класса ссылки, это очень ужасно-костыльно

Я пока вижу только догму: нельзя. Не вижу обоснования ни ужасности ни костыльности. Копировать/перемещать нельзя? Так чаще в сложных классах (с владением и аллокацией ресурсов /не только памяти/) это наоборот вредно/опасно.

Моя позиция: под задачу.

hatred ★★★
()
Ответ на: комментарий от hatred

Но концепты отрабатывают в компайл-тайме. Как делаешь проверку на null

Из core guidelines я взял только концепцию, не реализацию not_null’a, они есть разные, что-то там мелкомягкие еще говнокодили. Отвечу за свой not_null - концепт проверяет лишь то, что параметр шаблона - указатель, для однообразного стиля вида not_null<int *>. Никаких проверок нет, легко можно передать нуль, и узнать об этом лишь по скинутой корке. Мой not_null - это лишь метка, подсказка для программиста, мне этого хватает. Если тебе нужны zero cost гарантии отсутсвия null, то использовать надо std::reference_wrapper, его конструктор принимает на вход ссылку и передать nullptr никак не выйдет, но внутри будет храниться указатель. С точки зрения инициализации эта обертка полный аналог обычной ссылки. Думаю, что в std пропихнули более продуманную вещь, чем пытаться нагородить какие-то гарантии вокруг not_null.

Я пока вижу только догму: нельзя. Не вижу обоснования ни ужасности ни костыльности. Копировать/перемещать нельзя? Так чаще в сложных классах (с владением и аллокацией ресурсов /не только памяти/) это наоборот вредно/опасно.

Я обосновал уже. Если ты пишешь на цпп, а не Ц с классами, то move/copy само отвалится в нужных местах (как например упомянутый выше mutex), наличие обычной ссылки/указателя не должно быть причиной этого. Если у тебя там ресурс, то ты можешь, например, обернуть в unique_ptr и, возможно, дать свой deleter. Это плюсовй подход, а не куча говна в классе, которому на всякий случай делают

Удалю_ктр_на_всякий_случай(Удалю_ктр_на_всякий_случай &&) = delete;

Но, конечно, иногда уместно удалить каким-то глобальным объектам, например.

Ну и раз уж моего слова мало, могу лишь процитировать core guidelines:

«Note that using a reference member is almost always wrong»

Если и слов людей калибра Страуструпа мало, то мне и смысла нет здесь что-то писать

kvpfs_2
()