Я создаю собственный общий указатель на 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>();SharedPtr<Foo> f = ...; SharedPtr<FooDerived> fd = TryCast<FooDerived>(f);Как это можно реализовать?
@ 463035818_is_not_an_ai Я добавил ограничение шаблона, позволяющее заменять T только на Foo или один из его производных классов.
У вас будут проблемы с висящими счетчиками ссылок. Обратите внимание, что агрегация является опцией, вы все равно можете использовать std::shared_ptr в качестве переменной-члена. А затем предоставить конструкторы и т. д., которые ограничивают Foo (шаблоны не нужны)
Вам нужно T* находиться непосредственно в SharedPtr, а не возле реф-счетчика. Потому что, когда задействовано множественное наследование, после приведения указателя у вас может быть указатель с тем же указателем счетчика ссылок, но с другим указателем объекта.
Вам нужен конвертирующий конструктор, как в (9) ?
вам не нужно добавлять Edit1:... к вопросу. Если кто-то хочет просмотреть историю изменений, он может увидеть ее здесь: stackoverflow.com/posts/78371679/revisions (нажмите edited 42 mins ago)
Кроме того: std::is_base_of_v<Foo, Foo> правда, std::is_same_v вам не нужен.
опечатка: std::enable_if -> std::enable_if_t. Класс существует всегда, только псевдоним члена является ошибкой замены.





Я бы использовал такой подход:
#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
}
вы говорите, что он должен работать только с
Fooи его производными классами, но в коде есть аргумент шаблонаT. ТолькоFooзначительно проще обычногоT.