C++, полиморфизм и умные указатели

86
26 августа 2021, 15:30

Я плохо понимаю, как умные указатели (особенно shared_ptr) взаимодействуют с полиморфными типами. Общая картина кажется простой, но как только я начинаю разбираться в деталях, то просто тону в них.

Например, я не совсем уверен в том, что именно происходит "под капотом" в следующем коде:

shared_ptr<Base> get()
{
    return make_shared<Derived>();
}
int main(int argc, char **argv)
{
    shared_ptr<Base> base = get();
    return 0;
}

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

В случае с сырыми указателями или ссылками все тривиально:

void do_something(Base* base)
{
    Derived *derived = dynamic_cast<Derived *>(base);
    if (derived != nullptr)
    {
        // ...
    }
}

Но в случае с умными указателями (особенно, разделяемыми shared_ptr) все становится сложно, потому что это шаблонные классы, которые нековариантны.

Мне непонятно, чем отличается:

shared_ptr<Base> base = make_shared<Derived>();

И:

shared_ptr<Derived> base = make_shared<Derived>();

Также мне интересно, законно ли это, и есть ли тут потенциальные проблемы:

void do_something(shared_ptr<Base> base)
{
    // Принято ли так делать?
    Derived *derived = dynamic_cast<Derived *>(base.get());
    if (derived != nullptr)
    {
        // ...
  }
}
int main()
{
    shared_ptr<Base> base = make_shared<Derived>();
    do_something(base);
}
Answer 1

Умный указатель - это указатель, который умеет сам вызывать delete объекту.

Отсюда сразу следует два свойства:

  1. никогда нельзя вызывать delete объекту самостоятельно, если им владеет умный указатель;
  2. никогда нельзя создавать умный указатель на объект, которым уже владеет другой умный указатель.

Если же говорить о том что с ним можно делать - ответ будет "смотри его интерфейс".

Конкретно в случае shared_ptr вы можете делать с умным указателем почти всё что можно делать с указателем обычным: его можно копировать, приводить к указателю на базовый тип, также ему можно делать все 4 приведения типа через специальные функции (std::static_pointer_cast, std::dynamic_pointer_cast, std::const_pointer_cast и std::reinterpret_pointer_cast).

Также всегда можно получить сырой указатель через метод get(), и далее делать с ним что угодно кроме вызова delete. Но так поступать не рекомендуется: если вы широко используете умные указатели, то сырой указатель можно случайно "обернуть" в умный, что приведет к вызову delete.

READ ALSO
Как можно перевернуть число в C++?

Как можно перевернуть число в C++?

Для наглядности: на вход программе даётся число, к примеру 1234Нужно сделать так, чтобы на выходе вывелось число 4321

142
Анимация появления текста

Анимация появления текста

Не понимаю, как добиться такого эффекта появления текста

242
Двойной возврат по ссылке href=./

Двойной возврат по ссылке href=./

В октябрь смс рисую ссылку <0а href="/" >Ссылка назад<0/а>

89
Как зафиксировать элемент в конце первой строки во flex-контейнере

Как зафиксировать элемент в конце первой строки во flex-контейнере

Мне надо зафиксировать элемент в конце первой строки во flex-контейнере (который имеет flex-wrap: wrap)См

117