Pull to refresh
32
0

User

Send message

Спасибо.


все параметры передаются по значению без копирования. То есть если вы передали что-то в другую функцию без &, больше вы его использовать не сможете.

По сути, в плюсах — это аналог rvalue с мув семантикой. Но в плюсах нет поддержки овнершипа на уровне языка/компилятора:


std::string str = "I'm the owner";
foo(std::move(str)); // передали овнершип в функцию

Как я уже писал Halt — смотрю Rust из-за этих манипуляций с лайфтаймом :)

Спасибо за объяснения.


Структуры вида struct Pair(i32, f32); служат для другой цели. Их используют когда хочется связать несколько значений под одним общим именем типа, но отдельные поля именовать не хочется.

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


struct Meters(f32) — подтверждает это. На псевдо коде мы имеем что-то такого:


void foo(f32); // 1
void foo(Meters); // 2

foo(f32{0}); // 1
foo(Meters{0}); // 2

(Подправте, если ошибаюсь)


С макросами — понял — аналог шаблонов. Но тогда


Идентификаторы с крючком были введены, чтобы отличать их от других шаблонных параметров и чтобы их можно было аккуратно прилепить к ссылке:

есть и макросы (которые судя по комментариям ниже могут генерить код/модули) и есть шаблоны.


Спасибо за объяснения. Я фан явного указания жизни обьектов. Из-за вас теперь уже точно смотрю на Rust :)

Спасибо за комментарий.
Предупреждаю — ниже взгляд человека, который НЕ разбирался в языке совершенно ни грамма.


Навскидку:


name: &'static str,

этот маленький ' крючёчек для меня, как человека с плохим зрением — просто сложно заметен.
Непривычно смотрится:


&self, f: &mut Formatter

но это просто нужно один раз понять (я не разбирался что всё это значит ещё. Догадываюсь, что, возможно, в Rust-е всё const-by-default, поэтому &mut. А & — потому что, так же, как и в плюсах, есть возможность передавать по ссылке/по значению. Но опять же — это просто догадки).


Непривычно смотрится просто знак восклицания println! и всякие штуки, типа slice: &[i32].
Дальше:


// A tuple struct
struct Pair(i32, f32);

только с комментария догадываюсь, что это аналог алиаса (?): using Pair = std::tuple<i32, f32>; и, возможно, доступ будет происходить по индексам.


Ну, а этот код из статьи — это вообще ад:


macro_rules! __impl_slice_eq1 {
    ($Lhs: ty, $Rhs: ty) => {
        __impl_slice_eq1! { $Lhs, $Rhs, Sized }
    };

    ($Lhs: ty, $Rhs: ty, $Bound: ident) => {
        #[stable(feature = "rust1", since = "1.0.0")]
        impl<'a, 'b, A: $Bound, B> PartialEq<$Rhs> for $Lhs where A: PartialEq<B> {
            #[inline]
            fn eq(&self, other: &$Rhs) -> bool { self[..] == other[..] }
            #[inline]
            fn ne(&self, other: &$Rhs) -> bool { self[..] != other[..] }
        }
    }
}

Опять же — уверен, что если разобраться — всё становиться логичным.
Спасибо

Я настолько привык к C++, что жить без него не могу и уже не замечаю проблемы с синтаксисом )
Спасибо за советы. Пойду смотреть Rust

Не пытался садится за Rust всерьёз из-за синтаксиса. Он меня жутко пугает. Что посоветуете? Потерпеть сначала, а потом будет всё в порядке?

Не вы одни сдаётесь. Вон, Мейерс тоже сдался. Но это еще не означает, что C++ — уродливый. Вы на новые стандарты C — посмотрите. Там тоже можно за голову взяться. Это нормально для языков с историей, которые еще и развиваются

да, дело вкуса, разници нет. Лично я думаю, что static_cast() — слишком длинно, C-style каст — слишком незаметно, а странный void() — и в глаза падает (идешь гуглить, что это за void()), и не слишком длинный

Раз уж такая пьянка:


  1. Не учитываются пустые тюплы, нужно, для unused, хотя бы 1н элемент
  2. Не учитывается перегруженный operator,() для случая, когда F возвращает user-defined тип, для которого и определён operator,()
  3. Раз уж C++ 14, тогда и decay_t<T> вместо typename decay<T>::type (tuple_size_v можно аналогично заюзать, но уже C++ 17)
  4. decay<T> — слишком жёстко, просто — remove_reference<T>

template <typename F, typename T, size_t... i>
void tupleForeachImpl(F&& f, T&& t, index_sequence<i...>) {
    auto unused = { true, (f(get<i>(forward<T>(t))), void(), true)... };
}

template <typename F, typename T>
void tupleForeach(F&& f, T&& t) {
    tupleForeachImpl(forward<F>(f), forward<T>(t), make_index_sequence<tuple_size<remove_reference_t<T>>::value>());
}

Ну и, я думаю, стоило бы упомянуть C++ 17 fold expressions

Да, нужно использовать atomic-операции для свапа: C++ 11 std::atomic_...<std::shared_ptr>

Достойно. Спасибо за ссылку. Получил массу удовольствия :)

Спасибо за ответ.


если забыли конструктор, ты будет первое сообщение, если забыли деструктор — второе.

Я немного не понял вас. Вот моя логика: sizeof(T) — приведёт к ошибке компиляции, если компилятор не видет определения типа T, т.е., T — неполный тип. sizeof(T) никак не зависит от того определён для user defined типа конструктор или деструктор. Это означает, что sizeof(T) > 0 всегда — для нашого случая — нужен только для того, чтобы выдать пользователю ошибку во время компиляции о том, что он забыл определить указанный класс (тип) до места его использования (кстати, поскольку sizeof(T) никогда не может быть 0м — то можно просто писать sizeof(T)).
Дальше: у вас, по сути, таких места 2: конструктор и деструктор. Ставим вопрос — может ли пользователь написать такое использование PimplPtr<Impl>, чтобы Impl был, например, определён до вызова конструктора PimplPtr<Impl> и, одновременно, не определён при вызове деструктора PimplPtr<Impl>? (или наоборот). Ответ — да, может:


Случай первый: в конструкторе тип - неопределён, в деструкторе - уже определён
    template<typename T>
    struct PimplPtr
    {
        // 
    };

    // Header
    // 
    struct UserType
    {
        struct Impl;
        PimplPtr<Impl> _impl;

        UserType()
            // Используем неполный тип `Impl`
            : _impl{}
        {
        }

        ~UserType();
    };

    // Source
    // 

    struct UserType::Impl
    {
    };

    // Используем уже определённый тип `Impl`
    UserType::~UserType() = default;

Случай второй: в конструкторе тип - определён, в деструкторе - не определён
    // Header
    // 
    struct UserType
    {
        struct Impl;
        PimplPtr<Impl> _impl;

        UserType();

        ~UserType()
        {
            // Используем неполный тип `Impl`
        }
    };

    // Source
    // 

    struct UserType::Impl
    {
    };

    // Используем уже определённый тип `Impl`
    UserType::UserType()
        : _impl{}
    {
    }

Т.е., да — я вас обманул в деструкторе — для приведённого второго случая — такой assert будет полезен, прошу прощения. Но текст сообщения, всё же, немного неправильный.


По поводу делегирующего конструктора — я снова вас обманул — имел в виду, что 1й конструктор должен вызывать 2й, т.е., для вашего кода, это что-то типа:


PimplPtr(): PimplPtr(std::make_unique<T>()) {}

explicit PimplPtr(std::unique_ptr<T>&& p) noexcept: p_(std::move(p)) {}

И ещё — поскольку у вас в публичном классе есть функция:


PimplPtr(std::unique_ptr<T>&& p)

То это означает, что я могу сделать так:


PimplPtr<int>{std::unique_ptr<int>{nullptr}};

либо, более неявно — через какую-то фабрику:


PimplPtr<T>{make_T()};

где make_T() вернёт нулевой указатель.
Т.е. не хватает проверки времени выполнения инварианта указатель не nullptr — я бы добавил assert() в конструктор и ф-и get().


И, моё предвзятое мнение — в мире плюсов — для таких штук как умные указатели — пытаются убежать от неявных опеаторов преобразования: т.е. — пометить бы ещё operator ElementType() как explicit...


И… и я бы написал using ElementType = T — меньше кода и проще читается…
Извините, я увлёкся — простите за, возможно, нелепую критику — ухожу от клавиатуры :)
Спасибо

Я бы всё-же не приводил "не очень хороший" код в статье для новичков: в первом примере, раз вы явно говорите об деструкторе, то можно бы и явно прописать реализацию конструкторов/операторов присваивания, следуя хорошему тону — The rule of three/five/zero. auto_ptr я бы выбросил вообще — всё по тому же поводу — deprecated — можно просто ссылочку дать — так для истории и действительно интересующихся. Про unique_ptr, возможно, нужно было бы рассказать, почему деструктор нужно реализовывать в файле-реализации (а что с move-семантикой на этот счёт ?) — но это отклонение от темы — зачем нужна своя реализация PimplPtr-а?


Если убрать из статью вводную, то причина у вас, получается одна — "нарушение логической константности" (а точнее 2ве: "propagate_const пока не является частью стандарта"). Хорошо, но — код из статьи — не компилируется.


По поводу реализации:


  • static_assert — не нужен — это сделает за вас вызов конструктора в make_unique.
  • constexpr — зачем? Он сдесь только мешает код читать и нет случая, когда он нужен был бы.
  • 2й конструктор, который explicit (кстати, а почему? почему и не первый ?), наверное, должен вызывать первый (Delegating constructor) — хотя бы для того, чтобы логика в конструкторах совпадала
  • зачем в деструкторе делать тот-же statis_assert, когда в конструкторе — она уже есть ?
  • почему здесь = default используется, а в примерах выше, в файле-реализации — нет ?: Widget::~Widget() = default
  • хорошим тоном является использование одной и той же функции — основной — для реализации других ф-й, которые дублируют код для удобства, т.е. — реализация операторов должна использовать эталонный get()

Спасибо за статью. Извините за, возможно, резкий тон. Хорошего вам дня ^_^

Не, долго писать, но вкратце:


С использованием move(args)… компилятор попробует получить rvalue из всего переданного, и будет искать void (string &&, vector &&), опять промах. Остаётся использовать std::forward.

Если вы будете использовать std::forward() в этом контексте, то это аналогично std::move() для всех параметров. Т.е., с вашим примером, будет std::move() как для 1го аргумента, так и для второго, т.е., если была бы ещё перегрузка moveNamedVector(string&&, vector&&), то вызвалась бы именно она, независимо от того использовали ли бы вы std::move() (более идиоматично) либо std::forward() (нестандартное использование).


По поводу std::function, да, использование с аллокаторами задепрекейтили в C++17: Deprecating Allocator Support in std::function, потому что:


there are technical issues with storing an allocator in a type-erased context and then recovering that allocator later for any allocations needed during copy assignment.

Забавно

Допустим, у нас есть:


#include <cstdio>
#include <utility>

struct UserType
{
};

// (1)
void Concrete(UserType&&)
{
    std::puts("Concrete(UserType&&)");
}

// (2)
void Concrete(UserType&)
{
    std::puts("Concrete(UserType&)");
}

template<typename T>
void DoForward(T&& parameter)
{
    Concrete(std::forward<T>(parameter));
}

int main()
{
    UserType value;
    // 1: аргумент (@value) у нас T& (UserType&, lvalue),
    // а параметр (@parameter) - T&& - происходит наложение
    // & + && -> получаем UserType&.
    // Вызывается (2)я версия Concrete()
    DoForward(value);

    // 2: Передаём временный обьект - 
    // аргумент (@value) у нас T&& (UserType&&, грубо говоря, rvalue),
    // а параметр (@parameter), всё тот же, T&& - происходит наложение
    // && + && -> получаем UserType&&.
    // Вызывается (1)я версия Concrete()
    DoForward(UserType{});
}

parameter в DoForward(), поскольку это шаблонная функция, попадает в (не знаю как перевести) "deduced context" — и всё что в main-е я написал, это, как раз таки, "вывод типа" параметра, который учитывает "шаблонный" тип параметра и тип аргумента функции. Из-за того, что это шаблон, как видно, мы можем получить либо rvalue либо lvalue. Всё просто: "deduced context" — юзаем forward().


Как работает forward(): для первого вызова в main-е, как я уже написал, тип parameter вывелся в UserType&, т.е., T — это UserType&. forward() принимает аргументом (всегда!) — именованную переменную — т.е., это всегда lvalue. Получаем, что forward() имеет на вход шаблонный аргумент типа UserType& и тип параметра, так же, UserType& — всё это означает, что переданный на вход аргумент — это lvalue! forward() ничего не делает.
Аналогично, для второго вызова в main-е, тип parameter вывелся в UserType&&, т.е., T — это UserType&&. Получаем, что forward() имеет на вход шаблонный аргумент типа UserType&& и параметр UserType& — переданный на вход аргумент — это rvalue! forward() делает move().


Во всех остальных случаях — у нас нет "deduced context-а" и всё, каким мы его видим, таким и есть — т.е. T&& — это rvalue — нужно делать move(). T& — это ссылка — делаем что хотим, а T — хм, переменная, которая, больше не используется, поэтому можно сделать move().


Т.е., в случае:


template<typename T>
struct NonDeducedContext
{

    static void call(T parameter)
    {
        DoForward(std::move(parameter));
    }
}

для параметра функции call() нет вывода типа, потому что он уже выведен для класса в целом (мы его указываем при инстанциировании шаблона: NonDeducedContext<UserType>). Т.е., parameter — это value type — это копия аргумента функции call(). Ниже, по коду, он нигде не используется, поэтому я спокойно его муваю, тем самым говоря, что я его больше не использую и "делайте со мной что хотите".

Просто хочу заметить, что "если не сильно менять и причёсывать" код из статьи, то, как раз таки, std::forward() там нигде не нужно, просто потому что форвардинг ссылки нигде и не используются (а должны! К сожалению, и std::move() упущен… ладно).


По поводу вашей реализации, извините, я не сильно всматривался, вам не хватило аллокатора который есть в интерфейсе std::function?
Плюс, по поводу std::forward(), в этом месте — кхм, навскидку не понял, почему там std::forward(), а не std::move()? Судя по этому месту, я дико извиняюсь, — вы неправльно используете std::forward()?

Хм, мне кажется, я начинаю завидовать вашему code-style.
Да-да, если кодовая база однородна и всё такое, то, вроде как, всё хорошо, но как же меня бесит этот camelCase..

Как по мне — так тут один решающий недостаток — нельзя написать просто: std::vector<std::unique_ptr<IA>>

А зачем вы делаете new std::vector<T>? Это никак не влияет на скорость/пам'ять?

Information

Rating
Does not participate
Location
Киев, Киевская обл., Украина
Registered
Activity