Понижающий общий указатель на производный класс с дополнительными функциями - это безопасно? - PullRequest
13 голосов
/ 09 июня 2011

Рассмотрим следующую схему:

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 to shared_ptr ? : Это очень близко, однако класс dervied не добавляет дополнительных функций, как мой, поэтому я не уверен, что он полностью такой же. Кроме того, он использует 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 */

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

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

Ответы [ 3 ]

9 голосов
/ 09 июня 2011

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

3 голосов
/ 09 июня 2011

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

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

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

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

2 голосов
/ 09 июня 2011

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

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

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

...