Как привести мой собственный общий указатель в C++

Я создаю собственный общий указатель на C++, чтобы узнать, как все это работает.

Моя реализация не является универсальной, я хочу, чтобы она использовалась только с классом Foo или любым из его подклассов.

Вот как выглядят мои занятия:

template <typename T, typename = std::enable_if_t<std::is_same_v<T, Foo> || std::is_base_of_v<Foo, T>>>
class ReferenceCounter final
{
private:
    T* m_Value;
    unsigned int m_Counter;
};


template <typename T, typename = std::enable_if_t<std::is_same_v<T, Foo> || std::is_base_of_v<Foo, T>>>
class SharedPtr final
{
private:
    ReferenceCounter<T>* m_ReferenceCounter;
};

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

Теперь я хочу добавить две функции:

  • Я хочу иметь возможность неявно преобразовывать общий указатель из подкласса в базовый класс следующим образом: SharedPtr<Foo> f = MakeShared<FooDerived>();
  • Я хочу иметь возможность выполнять преобразование базового класса в производный класс, возвращая nullptr, если преобразование невозможно: SharedPtr<Foo> f = ...; SharedPtr<FooDerived> fd = TryCast<FooDerived>(f);

Как это можно реализовать?

вы говорите, что он должен работать только с Foo и его производными классами, но в коде есть аргумент шаблона T. Только Foo значительно проще обычного T.

463035818_is_not_an_ai 23.04.2024 12:49

@ 463035818_is_not_an_ai Я добавил ограничение шаблона, позволяющее заменять T только на Foo или один из его производных классов.

Bruno Jácome 23.04.2024 13:04

У вас будут проблемы с висящими счетчиками ссылок. Обратите внимание, что агрегация является опцией, вы все равно можете использовать std::shared_ptr в качестве переменной-члена. А затем предоставить конструкторы и т. д., которые ограничивают Foo (шаблоны не нужны)

Pepijn Kramer 23.04.2024 13:08

Вам нужно T* находиться непосредственно в SharedPtr, а не возле реф-счетчика. Потому что, когда задействовано множественное наследование, после приведения указателя у вас может быть указатель с тем же указателем счетчика ссылок, но с другим указателем объекта.

HolyBlackCat 23.04.2024 13:12

Вам нужен конвертирующий конструктор, как в (9) ?

pptaszni 23.04.2024 13:15

вам не нужно добавлять Edit1:... к вопросу. Если кто-то хочет просмотреть историю изменений, он может увидеть ее здесь: stackoverflow.com/posts/78371679/revisions (нажмите edited 42 mins ago)

463035818_is_not_an_ai 23.04.2024 13:17

Кроме того: std::is_base_of_v<Foo, Foo> правда, std::is_same_v вам не нужен.

Caleth 23.04.2024 13:20

опечатка: std::enable_if -> std::enable_if_t. Класс существует всегда, только псевдоним члена является ошибкой замены.

463035818_is_not_an_ai 23.04.2024 14:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
128
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я бы использовал такой подход:

#include <memory>
#include <type_traits>
#include <iostream>

//--------------------------------------------------------------------------------
// Foo and Bar structs 

struct Foo
{
    virtual ~Foo() = default;
    virtual void DoSomething() const
    {
        std::cout << "Foo\n";
    }
};

struct Derived : 
    public Foo
{
    void DoSomething() const override
    {
        std::cout << "Derived\n";
    }
};

struct Bar
{
};

//--------------------------------------------------------------------------------
// your own special shared pointer type
// Note, no inheritance here. But still reuse of std::shared_ptr

class shared_foo_ptr_t final
{
public:
    // make sure all the move and copy constructors are defined
    // since there is a custom (private) constructor
    ~shared_foo_ptr_t() = default;
    shared_foo_ptr_t(const shared_foo_ptr_t&) = default;
    shared_foo_ptr_t& operator=(shared_foo_ptr_t& ) = default;
    shared_foo_ptr_t(shared_foo_ptr_t&&) = default;
    shared_foo_ptr_t& operator=(shared_foo_ptr_t&&) = default;

    // here is your template function
    // with extra checks on Foo being a base
    template<typename type_t>
    static auto create() -> std::enable_if_t<std::is_base_of_v<Foo,type_t>, shared_foo_ptr_t>
    {
        shared_foo_ptr_t shared_foo_ptr{ std::make_shared<type_t>() };
        return shared_foo_ptr;
    }

    // explicit! casting operator
    explicit operator std::shared_ptr<Foo>() const noexcept
    {
        return m_foo_ptr;
    }

    auto& operator->()
    {
        return m_foo_ptr;
    }

    const auto& operator->() const
    {
        return m_foo_ptr;
    }

    auto* get() 
    {
        return m_foo_ptr.get();
    }

private:
    explicit shared_foo_ptr_t(std::shared_ptr<Foo>&& foo) :
        m_foo_ptr(foo)
    {
    }

    std::shared_ptr<Foo> m_foo_ptr;
};

// helper function that looks like std::make_shared
template<typename type_t>
auto make_shared_foo_ptr() -> std::enable_if_t<std::is_base_of_v<Foo,type_t>, shared_foo_ptr_t>
{
    return shared_foo_ptr_t::create<type_t>();
}


// show function using shared_foo_ptr_t
void fn(const shared_foo_ptr_t& shared_foo_ptr)
{
    shared_foo_ptr->DoSomething();
}

int main()
{
    auto foo_ptr = make_shared_foo_ptr<Derived>();
    fn(foo_ptr);

    //auto bar_ptr = make_shared_foo_ptr<Bar>(); // does not compile as it should

    // allow casting to a normal shared_ptr
    auto shared_ptr = static_cast<std::shared_ptr<Foo>>(foo_ptr);

    // then you have all the other shared_ptr facilities too
    auto derived_ptr = std::dynamic_pointer_cast<Derived>(shared_ptr);
    if ( derived_ptr != nullptr)
    {
        derived_ptr->DoSomething();
    }

}
Ответ принят как подходящий

Как указано в комментариях, с данной конструкцией это невозможно. SharedPtr<Foo> должен будет хранить указатель на ReferenceCounter<FooDerived>, предоставленный MakeShared<FooDerived>, что невозможно. Чтобы эта работа работала, механика подсчета ссылок не должна быть шаблонной.

class ReferenceCounter {
    ...
    unsigned m_Count;
};

template<class T, ...>
class SharedPtr {
    ...
    T* m_Value;
    ReferenceCounter* m_Ref;
};

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

// Within the SharedPtr class

template<class Other, /* Only if T is a base of Other */>
SharedPtr(const SharedPtr<Other>& ptr) :
    m_Value(ptr.m_Value),
    m_Ref(ptr.m_Ref)
{
    // Increment the reference count, remember to check if ptr holds a non-null value/ref.
    ++m_Ref.m_Count;
}

// Also remember to friend the other class instantiations of SharedPtr
template<class Other>
friend class SharedPtr;

Чтобы преобразовать базу в производную, вам придется попробовать что-нибудь с dynamic_cast.

template<class To, class From, /* Only if Foo is a base of To and From */>
auto TryCast(const SharedPtr<From>& ptr)
{
    auto* value = /* Retreive m_Value from ptr */
    auto* ref = /* Retreive m_Ref from ptr */

    auto* new_value = dynamic_cast<To*>(value);
    if (!new_value)
        return SharedPtr<To>(nullptr);

    return /* SharedPtr<To> with m_Value = new_value and m_Ref = ref */
    // Remember to adjust the refcount
}

Другие вопросы по теме