Подтвердить что ты не робот

Даунсайдинг, общий указатель на производный класс с дополнительной функциональностью - это безопасно?

Рассмотрим следующий контур:

class Base { /* ... */ };

class Derived : public Base
{
public:
    void AdditionalFunctionality(int i){ /* ... */ }
};

typedef std::shared_ptr<Base> pBase;
typedef std::shared_ptr<Derived> pDerived;

int main(void)
{
    std::vector<pBase> v;
    v.push_back(pBase(new Derived()));

    pDerived p1(  std::dynamic_pointer_cast<Derived>(v[0])  ); /* Copy */
    pDerived p2 = std::dynamic_pointer_cast<Derived>(v[0]);    /* Assignment */

    p1->AdditionalFunctionality(1);
    p2->AdditionalFunctionality(2);

    /* A */

    return 0;
}

Здесь я расширяю базовый класс с производным классом, который добавляет функциональность (метод AdditionalFunctionality).

Первый вопрос, это нормально? Я прочитал много вопросов, которые говорят, что это не нормально, и вы должны объявить дополнительные функциональные возможности базового класса (часто предлагаемые как создание их чистых виртуальных методов в базовом классе). Однако я не хочу этого делать. Я хочу расширить функциональность базового класса, а не просто реализовать его по-разному. Есть ли лучшее решение для достижения этой цели?

Хорошо, поэтому в этом коде я также использую контейнер STL для хранения этих указателей, который позволяет мне хранить указатели на оба объекта типа Base, а также объекты типа Derived без нарезки объектов.

Второй вопрос, это имеет смысл, не так ли? Я, по сути, избегаю резки с помощью указателей на объекты базового класса, а не на объекты базового класса?

Если я "знаю", что определенный указатель относится к производному объекту, я затем использую std::dynamic_pointer_cast для использования умного указателя.

Третий вопрос, это компилируется без предупреждения и работает, но безопасно ли это? Действительно? Изменит ли он ссылочный аспект общих указателей и не сможет delete мои объекты или delete их до того, как я ожидаю?

Наконец, я могу сделать это, используя либо конструктор копирования, либо через назначение, как показано для p1 и p2. Есть ли предпочтительный/правильный способ сделать это?

Похожие вопросы:

  • Downcasting shared_ptr <Base> to shared_ptr <Derived> ?: Это очень близко, однако удеренный класс не добавляет дополнительных функций, подобных моим, поэтому я не уверен, что он полностью совпадает. Кроме того, он использует boost::shared_ptr, где я использую std::shared_ptr (хотя я понимаю, что boost пожертвовал shared_ptr в std-библиотеку, поэтому они, вероятно, совпадают).

Благодарим вас за помощь.


Edit:

Одна из причин, по которой я спрашиваю, заключается в том, что я понимаю, что можно сделать следующее (неправильно):

    /* Intentional Error */
    v.push_back(pBase(new Base()));
    pDerived p3( std::dynamic_pointer_cast<Derived>(v[1]) );
    p3->AdditionalFunctionality(3); /* Note 1 */

Где я пытаюсь сжать указатель на объект Base на указатель объекта Derived и затем вызвать метод, который реализуется только в классе Derived. Другими словами, объект, на который указывает, не определяет (или даже не "знает" метод).

Это не улавливается компилятором, но может вызвать segfault в зависимости от того, как определяется AdditionalFunctionality.

4b9b3361

Ответ 1

Имеет ли Base виртуальный деструктор? Если да, то безопасно использовать downcasting. В вашем неправильном примере pDerived должен быть NULL в результате, поэтому вам нужно каждый раз проверять результат dynamic_pointer_cast.

Ответ 2

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

Если контейнер может иметь оба типа объектов, то кажется, что вы хотите иметь возможность обрабатывать все объекты в качестве базового класса внутри этого контейнера. В этом случае вы почти наверняка хотите использовать полиморфизм для правильной работы: иметь виртуальный интерфейс, который в основном говорит "Выполняйте эту работу", а родительская версия вообще ничего не может сделать. Затем дочерняя версия метода реализует дополнительную функциональность, которая вам нужна.

Я думаю, что у вас может быть запах кода, что ваши объекты менее связаны, чем вы думаете. Наследуются ли вы для повторного использования или для замены? Вы также можете пересмотреть то, как выглядит ваш публичный интерфейс.

Все, что сказал, если вы решите продолжить свой нынешний проект (который я бы хотя бы решительно рассмотрел), я думаю, что ваше downcasting должно быть безопасным, пока вы проверяете результат динамического переноса для ненулевого значения, прежде чем использовать его.

Ответ 3

ОК, прежде всего, если вы это сделаете, вы захотите убедиться, что Base имеет виртуальный деструктор. В противном случае вы получите поведение undefined, когда вектор выходит за рамки. (Деструктор вектора вызовет деструктор Base для каждого из его элементов. Если какие-либо элементы действительно являются Derived - KABOOM!) Другое, что то, что вы написали, абсолютно безопасно и достоверно.

Но какой смысл? Если у вас есть контейнер объектов, вы хотите иметь возможность обрабатывать их все равно. (Зациклируйте на них все и вызовите функцию на каждом или что-то еще.) Если вы не хотели относиться к ним одинаково, зачем ставить их в один контейнер? Итак, у вас есть вектор, который может содержать указатели на Base или указатели на Derived - откуда вы знаете, какие элементы имеют тип? Планируете ли вы просто называть dynamic_cast для каждого элемента каждый раз, когда вы хотите вызвать AdditionalFunctionality, чтобы проверить, действительно ли элемент указывает на Derived? Это ни эффективное, ни идиоматическое, и это в основном побеждает весь смысл использования наследования. Вы могли бы просто использовать тегированный союз.

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